score:128

Accepted answer

.Select has a seldom-used overload that produces an index. You can use it like this:

str.Select((s, i) => new {i, s})
    .Where(t => t.s == "avg")
    .Select(t => t.i)
    .ToList()

The result will be a list containing 2 and 4.

Documentation here

score:0

You need a combined select and where operator, comparing to accepted answer this will be cheaper, since won't require intermediate objects:

public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> filter, Func<TSource, int, TResult> selector)
        {
            int index = -1;
            foreach (var s in source)
            {
                checked{ ++index; }
                if (filter(s))
                    yield return selector(s, index);
            }
        }

score:2

While you could use a combination of Select and Where, this is likely a good candidate for making your own function:

public static IEnumerable<int> Indexes<T>(IEnumerable<T> source, T itemToFind)
{
    if (source == null)
        throw new ArgumentNullException("source");

    int i = 0;
    foreach (T item in source)
    {
        if (object.Equals(itemToFind, item))
        {
            yield return i;
        }

        i++;
    }
}

score:4

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, str.Count)
                 .Where(i => str[i] == "avg")
                 .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.

score:7

You can use the overload of Enumerable.Select that passes the index and then use Enumerable.Where on an anonymous type:

List<int> result = str.Select((s, index) => new { s, index })
                      .Where(x => x.s== "avg")
                      .Select(x => x.index)
                      .ToList();

If you just want to find the first/last index, you have also the builtin methods List.IndexOf and List.LastIndexOf:

int firstIndex = str.IndexOf("avg");
int lastIndex = str.LastIndexOf("avg");

(or you can use this overload that take a start index to specify the start position)

score:23

You can do it like this:

str.Select((v,i) => new {Index = i, Value = v}) // Pair up values and indexes
   .Where(p => p.Value == "avg") // Do the filtering
   .Select(p => p.Index); // Keep the index and drop the value

The key step is using the overload of Select that supplies the current index to your functor.


Related Query

More Query from same tag