How to find a sub-list of two consecutive items in a list using Linq

前端 未结 5 1659
长发绾君心
长发绾君心 2021-01-26 19:56

Suppose that we have a list of strings like

\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"

Now I\'d like to search for a sub-list of

相关标签:
5条回答
  • 2021-01-26 20:45

    I don't think LINQ is appropriate here.

    A more efficient solution could be to find "D" then just check it isn't at the end and that "E" is at the next index:

    int i = list.FindIndex(x => x == "D");
    int p = (i < list.Count - 1) && (list[i + 1] == "E") ? i : -1;
    

    This avoids looping twice to find both indices, and also still matches if "E" appears next to "D" but also before it, e.g. {"E", "C", "D", "E"}

    0 讨论(0)
  • You could rewrite your approach as follows:

    bool hasSublist = list
        .SkipWhile(x => x != "D")
        .Take(2)
        .SequenceEquals(new[] {"D", "E"});
    

    If you need the starting index of {"D", "E"}, you could add a select that pairs up letters and their indexes.

    However, the problem with your approach is that it would miss the subsequence if there's another "D" not followed by an "E", for example

    "D" "A" "D" "B" "D" "C" "D" "D" "D" "E"
    

    There is a "D" "E" at the end, but your approach stops after finding the first "D".

    If you are looking for sublists of length 2, you could use Zip, like this:

    bool hasSublist = list
        .Zip(list.Skip(1), (a, b) => new {a, b})
        .Any(p => p.a == "D" && p.b == "E");
    

    However, this does not scale for longer sub-lists.

    Using a plain for loop would work much better:

    for (var i = 0 ; i < list.Count-1 ; i++) {
        if (list[i] == "D" && list[i+1] == "E") {
            ...
        }
    }
    

    The if inside could be replaced with SequenceEquals, accommodating sub-lists of any length:

    var desiredSublist = new[] {"D", "E", "F"};
    for (var i = 0 ; i < list.Count-desiredSublist+1 ; i++) {
        if (list.Skip(i).SequenceEquals(desiredSublist)) {
            ...
        }
    }
    
    0 讨论(0)
  • 2021-01-26 20:50

    The most elegant solution is the simplest

    Where((x,i) => (x == "D") && (i != list.Count - 1) && (list[i + 1] == "E")).FirstOrDefault();

    0 讨论(0)
  • 2021-01-26 20:53

    Not a good general or efficient answer, but concise - as it is a list of strings / characters, you could do a simple string.Join() and check for the substring:

    int p = string.Join("", list).IndexOf("DE");
    
    0 讨论(0)
  • 2021-01-26 20:56

    I didn't see a shorter solution than yours. But I think the solution you propose only works if first string doesn't appear twice in the list. For example, if the list was:

    "A", "D", "B", "C", "D", "E", "F"
    

    I think your proposal will not work because first FindIndex will return the index of the first "D", which is not followed by "E".

    A posible alternative could be (should be tested to be sure):

        int index=-1;
        Parallel.For(0, list.Count - 1, i =>
        {
          if (list[i] == "D" && list[i + 1] == "E")
          {
              Interlocked.Exchange(ref index, i);
          }
        }); 
    //after the loop, if index!=-1, sublist was found and starts at index position
    

    Not shorter, of course, but can be faster if list is very large, because using Parallel.For. A limitation is that if the sublist appears several times, you can obtain the index of any of them (not necessarily the first one).

    0 讨论(0)
提交回复
热议问题