457

Given a datasource like that:

var c = new Car[]
{
  new Car{ Color="Blue", Price=28000},
  new Car{ Color="Red", Price=54000},
  new Car{ Color="Pink", Price=9999},
  // ..
};

How can I find the index of the first car satisfying a certain condition with LINQ?

EDIT:

I could think of something like this but it looks horrible:

int firstItem = someItems.Select((item, index) => new    
{    
    ItemName = item.Color,    
    Position = index    
}).Where(i => i.ItemName == "purple")    
  .First()    
  .Position;

Will it be the best to solve this with a plain old loop?

codymanix
  • 28,510
  • 21
  • 92
  • 151
  • Similar: [get-list-element-position-in-c-sharp-using-linq](http://stackoverflow.com/questions/1110789/get-list-element-position-in-c-sharp-using-linq) – nawfal May 19 '14 at 17:14
  • Even this information would be helpful - http://stackoverflow.com/questions/4049773/how-to-use-index-position-with-where-in-linq-query-language – RBT Jul 28 '16 at 01:24
  • 10
    Actually there is an `index` statement: `var result = items.Select((item, index) => new { index, item });` – TaW Jun 11 '18 at 13:53

7 Answers7

935
myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;

or the slightly shorter

myCars.Select((car, index) => new {car, index}).First(myCondition).index;

or the slightly shorter shorter

myCars.Select((car, index) => (car, index)).First(myCondition).index;
Yuriy Faktorovich
  • 67,283
  • 14
  • 105
  • 142
  • 5
    I just used this and it works fine for me. how does this differ from the marked answer? – Aaron Anodide Mar 01 '11 at 00:27
  • You are using ordered collection (such as Car[]), thus marked answer doesn't apply to your case... you couldn't use this on Dictionary or Hashset. – nikib3ro Aug 18 '11 at 22:25
  • 2
    @kape123: This certainly also can be used for `Dictionary` and `Hashset`. Obviously the index returned isn't as "well defined" as an index in an ordered collection, but it can still be used with `ElementAt` to retrieve the matched element. – Martin Liversage Oct 14 '11 at 08:23
  • 14
    The big difference is that this will throw an exception if myCondition is not met by any of the items where as the marked answer returns -1 in that case. – juharr Feb 03 '12 at 15:54
  • 6
    @ProfK be careful with the `FirstOrDefault`, the default of a class in null, calling .index on a null with throw an exception. – Yuriy Faktorovich Sep 26 '12 at 15:26
  • 1
    @AaronAnodide, see juharr's comment. I recommend against using this method if you want to be able to handle cases where no items match. – Sam Jun 04 '13 at 05:53
  • 48
    Just commenting for future searchers that C#6 will allow `myCars.Select((car, index) => new {car, index}).FirstOrDefault(myCondition)?.index;` to return a null index when handling cases where there are no results after myCondition is applied. – MCattle Nov 05 '14 at 16:03
  • 1
    Handing ´myCondition´ to ´FirstOrDefault()´ means the function must accept an anonymous type, so ´a => myCondition(a.car)´ should be handed over. I used this implementation and made the result nullable, which is fine for me. But you could use ´?.index ?? -1´ to get the same result of the marked answer when the condition does not hit at all. – Patrik Jul 17 '20 at 07:11
222

Simply do :

int index = List.FindIndex(your condition);

E.g.

int index = cars.FindIndex(c => c.ID == 150);
joce
  • 9,624
  • 19
  • 56
  • 74
Red Swan
  • 15,157
  • 43
  • 156
  • 238
  • 5
    +1 - although LINQ deals only with IEnumerable, this answer made me realize that in my case it was OK to convert the IEnumerable to list and then call `FindIndex` – beluchin Dec 29 '13 at 06:39
  • 21
    For arrays just use `Array.FindIndex`. – xmedeko Feb 18 '16 at 12:27
  • 12
    @beluchin Keep in mind, if you convert the `IEnumerable` to `List` the `IEnumerable` is not lazy any more. You are forcing to obtain **Every** elements of it, even you don't actually need them. – Earth Engine Feb 29 '16 at 22:59
  • This is a perfect solution, as long as you're using a unique condition. In other cases, when it is possible that multiple elements match, you will not get a list of indices, but only the first element of it. – Matt Dec 03 '20 at 14:05
149

An IEnumerable is not an ordered set.
Although most IEnumerables are ordered, some (such as Dictionary or HashSet) are not.

Therefore, LINQ does not have an IndexOf method.

However, you can write one yourself:

///<summary>Finds the index of the first item matching an expression in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="predicate">The expression to test the items against.</param>
///<returns>The index of the first matching item, or -1 if no items match.</returns>
public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate) {
    if (items == null) throw new ArgumentNullException("items");
    if (predicate == null) throw new ArgumentNullException("predicate");

    int retVal = 0;
    foreach (var item in items) {
        if (predicate(item)) return retVal;
        retVal++;
    }
    return -1;
}
///<summary>Finds the index of the first occurrence of an item in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="item">The item to find.</param>
///<returns>The index of the first matching item, or -1 if the item was not found.</returns>
public static int IndexOf<T>(this IEnumerable<T> items, T item) { return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i)); }
Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 88
    Although it does have an ElementAt method. Which takes an index as the argument. – Yuriy Faktorovich Mar 18 '10 at 16:37
  • 1
    Yes; I don't know why that method exists. – SLaks Mar 18 '10 at 16:38
  • C# has a Predicate delegate for you, how come you used Func? – Yuriy Faktorovich Mar 18 '10 at 16:42
  • 6
    Because that's what all of the other LINQ methods use. It makes the delegate signature clearer in the tooltip. `Predicate`, `Comparison`, and friends were effectively replaced by the `Func` delegates in .Net 3.5. – SLaks Mar 18 '10 at 16:45
  • 5
    @SLaks, since this method already exists and is well known in the `List` class, you should name it `FindIndex` and it should take a `Predicate` instead of `Func`. The `IndexOf` takes a `T` object instead of a selector. Remember, consistency is the #1 strong point of the CLI: http://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx – Sam Harwell Mar 18 '10 at 17:18
  • 9
    @280Z28: LINQ is already inconsist with `List` - `FindAll(Predicate)` vs. `Where(Func)`, `Exists(Predicate)` vs. `Any(Func)`, `ConvertAll(Converter)` vs. `Select(Func)`, etc. – SLaks Mar 18 '10 at 17:28
  • 2
    @SLaks: That may be true, but 1) in each case, the new name does not alias a name present in a similar context earlier versions, 2) for FindAll/Where and ConvertAll/Select, the new version has different semantics, and the old name doesn't make sense in a lazy-evaluation context, and 3) for Exists/Any, the old name is misleading. You use this as a rationale for your answer, but you didn't follow #1, #2 certainly doesn't apply, and #3 is debatable at best. – Sam Harwell Mar 19 '10 at 01:04
  • 18
    @SLaks: With all the other methods that rely on the order (ElementAt, First, Last, Skip and friends) I don't think IndexOf would be too far-fetched. – Matti Virkkunen May 14 '12 at 12:39
  • 9
    The first bit is confusing and should be rephrased/deleted. `IEnumerable` exposes an `IEnumerator`, which has two members: `MoveNext()` and `Current` - it's inherently ordered. – Tim Rogers Nov 14 '16 at 12:44
  • This is already found in the Metadata of System.Collections.Generic – morethanyell Feb 12 '18 at 19:31
90
myCars.TakeWhile(car => !myCondition(car)).Count();

It works! Think about it. The index of the first matching item equals the number of (not matching) item before it.

Story time

I too dislike the horrible standard solution you already suggested in your question. Like the accepted answer I went for a plain old loop although with a slight modification:

public static int FindIndex<T>(this IEnumerable<T> items, Predicate<T> predicate) {
    int index = 0;
    foreach (var item in items) {
        if (predicate(item)) break;
        index++;
    }
    return index;
}

Note that it will return the number of items instead of -1 when there is no match. But let's ignore this minor annoyance for now. In fact the horrible standard solution crashes in that case and I consider returning an index that is out-of-bounds superior.

What happens now is ReSharper telling me Loop can be converted into LINQ-expression. While most of the time the feature worsens readability, this time the result was awe-inspiring. So Kudos to the JetBrains.

Analysis

Pros

  • Concise
  • Combinable with other LINQ
  • Avoids newing anonymous objects
  • Only evaluates the enumerable until the predicate matches for the first time

Therefore I consider it optimal in time and space while remaining readable.

Cons

  • Not quite obvious at first
  • Does not return -1 when there is no match

Of course you can always hide it behind an extension method. And what to do best when there is no match heavily depends on the context.

Jonas Bötel
  • 4,452
  • 1
  • 19
  • 28
  • 1
    adding simple isFound boolean before loop, setting this variable to true before break statement and comparing to it before returning from function would solve -1 problem – fex Feb 11 '13 at 21:15
  • I actually needed a LINQ query that would return the full count if no item was found, so this is perfect! – Paul Chernoch Jun 02 '15 at 16:38
  • 8
    `return index` instead of `break` inside loop would retain functionality and readability and make easy to convert last `return` to return -1 if no element is found. – Emperor Orionii Nov 04 '15 at 07:34
14

I will make my contribution here... why? just because :p Its a different implementation, based on the Any LINQ extension, and a delegate. Here it is:

public static class Extensions
{
    public static int IndexOf<T>(
            this IEnumerable<T> list, 
            Predicate<T> condition) {               
        int i = -1;
        return list.Any(x => { i++; return condition(x); }) ? i : -1;
    }
}

void Main()
{
    TestGetsFirstItem();
    TestGetsLastItem();
    TestGetsMinusOneOnNotFound();
    TestGetsMiddleItem();   
    TestGetsMinusOneOnEmptyList();
}

void TestGetsFirstItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("a"));

    // Assert
    if(index != 0)
    {
        throw new Exception("Index should be 0 but is: " + index);
    }

    "Test Successful".Dump();
}

void TestGetsLastItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("d"));

    // Assert
    if(index != 3)
    {
        throw new Exception("Index should be 3 but is: " + index);
    }

    "Test Successful".Dump();
}

void TestGetsMinusOneOnNotFound()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d" };

    // Act
    int index = list.IndexOf(item => item.Equals("e"));

    // Assert
    if(index != -1)
    {
        throw new Exception("Index should be -1 but is: " + index);
    }

    "Test Successful".Dump();
}

void TestGetsMinusOneOnEmptyList()
{
    // Arrange
    var list = new string[] {  };

    // Act
    int index = list.IndexOf(item => item.Equals("e"));

    // Assert
    if(index != -1)
    {
        throw new Exception("Index should be -1 but is: " + index);
    }

    "Test Successful".Dump();
}

void TestGetsMiddleItem()
{
    // Arrange
    var list = new string[] { "a", "b", "c", "d", "e" };

    // Act
    int index = list.IndexOf(item => item.Equals("c"));

    // Assert
    if(index != 2)
    {
        throw new Exception("Index should be 2 but is: " + index);
    }

    "Test Successful".Dump();
}        
Marcel Valdez Orozco
  • 2,985
  • 1
  • 25
  • 24
6

Here is a little extension I just put together.

public static class PositionsExtension
{
    public static Int32 Position<TSource>(this IEnumerable<TSource> source,
                                          Func<TSource, bool> predicate)
    {
        return Positions<TSource>(source, predicate).FirstOrDefault();
    }
    public static IEnumerable<Int32> Positions<TSource>(this IEnumerable<TSource> source, 
                                                        Func<TSource, bool> predicate)
    {
        if (typeof(TSource) is IDictionary)
        {
            throw new Exception("Dictionaries aren't supported");
        }

        if (source == null)
        {
            throw new ArgumentOutOfRangeException("source is null");
        }
        if (predicate == null)
        {
            throw new ArgumentOutOfRangeException("predicate is null");
        }
        var found = source.Where(predicate).First();
        var query = source.Select((item, index) => new
            {
                Found = ReferenceEquals(item, found),
                Index = index

            }).Where( it => it.Found).Select( it => it.Index);
        return query;
    }
}

Then you can call it like this.

IEnumerable<Int32> indicesWhereConditionIsMet = 
      ListItems.Positions(item => item == this);

Int32 firstWelcomeMessage ListItems.Position(msg =>               
      msg.WelcomeMessage.Contains("Hello"));
joce
  • 9,624
  • 19
  • 56
  • 74
jwize
  • 4,230
  • 1
  • 33
  • 51
4

Here's an implementation of the highest-voted answer that returns -1 when the item is not found:

public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
    var itemsWithIndices = items.Select((item, index) => new { Item = item, Index = index });
    var matchingIndices =
        from itemWithIndex in itemsWithIndices
        where predicate(itemWithIndex.Item)
        select (int?)itemWithIndex.Index;

    return matchingIndices.FirstOrDefault() ?? -1;
}
Sam
  • 40,644
  • 36
  • 176
  • 219