Get indexes of all matching values from list using Linq

39,002

Solution 1

First off, your code doesn't actually iterate over the list twice, it only iterates it once.

That said, your Select is really just getting a sequence of all of the indexes; that is more easily done with Enumerable.Range:

var result = Enumerable.Range(0, lst1.Count)
             .Where(i => lst1[i] == "a")
             .ToList();

Understanding why the list isn't actually iterated twice will take some getting used to. I'll try to give a basic explanation.

You should think of most of the LINQ methods, such as Select and Where as a pipeline. Each method does some tiny bit of work. In the case of Select you give it a method, and it essentially says, "Whenever someone asks me for my next item I'll first ask my input sequence for an item, then use the method I have to convert it into something else, and then give that item to whoever is using me." Where, more or less, is saying, "whenever someone asks me for an item I'll ask my input sequence for an item, if the function say it's good I'll pass it on, if not I'll keep asking for items until I get one that passes."

So when you chain them what happens is ToList asks for the first item, it goes to Where to as it for it's first item, Where goes to Select and asks it for it's first item, Select goes to the list to ask it for its first item. The list then provides it's first item. Select then transforms that item into what it needs to spit out (in this case, just the int 0) and gives it to Where. Where takes that item and runs it's function which determine's that it's true and so spits out 0 to ToList, which adds it to the list. That whole thing then happens 9 more times. This means that Select will end up asking for each item from the list exactly once, and it will feed each of its results directly to Where, which will feed the results that "pass the test" directly to ToList, which stores them in a list. All of the LINQ methods are carefully designed to only ever iterate the source sequence once (when they are iterated once).

Note that, while this seems complicated at first to you, it's actually pretty easy for the computer to do all of this. It's not actually as performance intensive as it may seem at first.

Solution 2

This works, but arguably not as neat.

var result = list1.Select((x, i) => new {x, i})
                  .Where(x => x.x == "a")
                  .Select(x => x.i);

Solution 3

How about this one, it works pretty fine for me.

   static void Main(string[] args)
    {
        List<char> Lst1 = new List<char>();
        Lst1.Add('a'); 
        Lst1.Add('a');   
        Lst1.Add('b');   
        Lst1.Add('b');   
        Lst1.Add('c');   
        Lst1.Add('b');   
        Lst1.Add('a');   
        Lst1.Add('c');
        Lst1.Add('a');

        var result = Lst1.Select((c, i) => new { character = c, index = i })
                         .Where(list => list.character == 'a')
                         .ToList();
    }
Share:
39,002
John Bustos
Author by

John Bustos

Updated on November 05, 2020

Comments

  • John Bustos
    John Bustos over 3 years

    Hey Linq experts out there,

    I just asked a very similar question and know the solution is probably SUPER easy, but still find myself not being able to wrap my head around how to do this fairly simple task in the most efficient manner using linq.

    My basic scenario is that I have a list of values, for example, say:

    Lst1:
    a
    a
    b
    b
    c
    b
    a
    c
    a
    

    And I want to create a new list that will hold all the indexes from Lst1 where, say, the value = "a". So, in this example, we would have:

    LstIndexes:
    0
    1
    6
    8
    

    Now, I know I can do this with Loops (which I would rather avoid in favor of Linq) and I even figured out how to do this with Linq in the following way:

    LstIndexes= Lst1.Select(Function(item As String, index As Integer) index) _
                    .Where(Function(index As Integer) Lst1(index) = "a").ToList
    

    My challenge with this is that it iterates over the list twice and is therefore inefficient.

    How can I get my result in the most efficient way using Linq?

    Thanks!!!!

  • John Bustos
    John Bustos over 11 years
    Servy, thank you for adding in that explanation - That really does help me understand things a lot more!!! Truly, thank you so much!!!
  • Servy
    Servy over 11 years
    This will result in a list of an anonymous type with both the index and the character; he just needs the index.
  • Servy
    Servy over 11 years
    @JohnBustos For the record my code won't really take any more or less time to execute than yours, it'll just be clearer to the reader and is take up less code on the screen.
  • tukaef
    tukaef over 11 years
    @Servy, huh, ...Select(o => o.index); :)
  • Servy
    Servy over 11 years
    @2kay Or you could just use the solution that I proposed so that you don't have two selects that perform and then undo a mapping.
  • Dean Radcliffe
    Dean Radcliffe over 11 years
    Actually @Servy, Enumerable.Range(0, lst1.Count) is proper, because Range automagically excludes the highest index. The code as you've written will miss the '8' from its output.
  • Dean Radcliffe
    Dean Radcliffe over 11 years
    Caveat- my explanation for why Enumerable.Range behaved that way wasn't quite right, but I don't think anyone noticed :)
  • Servy
    Servy over 11 years
    @DeanRadcliffe Yeah, I did notice, but since I was the one who had code that wasn't working as an answer I didn't feel right calling you out on it. The reason of course is that the second parameter isn't an end index, it's a number of items to be in the resulting sequence. Since it starts a 0 (first parameter) it means that the last value returned will be the count - 1.
  • StupidOne
    StupidOne over 10 years
    +1 because it's so much easier to read and understand what query does compared to other two answers