Как получить индекс с помощью LINQ? [дубликат]

этот вопрос уже есть ответ здесь:

учитывая такой источник данных:

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

Как найти индекс первого автомобиля, удовлетворяющего определенному условию с LINQ?

EDIT:

я мог бы придумать что-то подобное, но это выглядит ужасно:

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

будет ли лучше решить это с помощью простого старого цикла?

7 ответов


An IEnumerable не является упорядоченным набором.
Хотя большинство IEnumerables упорядочены, некоторые (например,Dictionary или HashSet) не являются.

таким образом, LINQ не имеет IndexOf метод.

тем не менее, вы можете написать один самостоятельно:

///<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)); }

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

или меньше

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

просто :

int index = List.FindIndex(your condition);

Е. Г.

int index = cars.FindIndex(c => c.ID == 150);

myCars.TakeWhile(car => !myCondition(car)).Count();

это работает! Думать об этом. Индекс первого совпадающего элемента равен количеству (не совпадающего) элемента перед ним.

история времени

я тоже не люблю ужасное стандартное решение вы уже предложили в своем вопросе. Как и принятый ответ, я пошел на простой старый цикл, хотя и с небольшой модификацией:

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;
}

обратите внимание, что он вернет количество элементов вместо -1 когда нет матча. Но давайте игнорировать это раздражает сейчас. На самом деле ужасное стандартное решение сбой в этом случае и я рассматриваю возможность возврата индекса, который выходит за рамки superior.

что происходит сейчас ReSharper говорит мне цикл может быть преобразован в LINQ-expression. Хотя в большинстве случаев функция ухудшает читаемость, на этот раз результат был впечатляющим. Так что спасибо В JetBrains.

анализ

плюсы

  • Краткий
  • Combinable с другим LINQ
  • избежать newИнг анонимные объекты
  • оценивает только перечисляемое до тех пор, пока предикат не совпадет в первый раз

поэтому я считаю его оптимальным во времени и пространстве, оставаясь читабельным.

минусы

  • сначала не совсем очевидно
  • тут не возвращайся -1 когда нет матча

конечно, вы всегда можете скрыть его за методом расширения. И что делать лучше всего, когда нет совпадения, сильно зависит от контекста.


Я сделаю свой вклад здесь... почему? просто потому, что: p его другая реализация, основанная на любом расширении LINQ, и делегат. Вот это:

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();
}        

вот небольшое расширение, которое я только что собрал.

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;
    }
}

затем вы можете назвать это так.

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

Int32 firstWelcomeMessage ListItems.Position(msg =>               
      msg.WelcomeMessage.Contains("Hello"));

вот реализация ответа с самым высоким голосованием, который возвращает -1, когда элемент не найден:

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;
}