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


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

  • Получить List положение элемента в c# с помощью LINQ 9 ответов
  • Как получить индекс элемента в IEnumerable? 11 ответов

приведен источник данных, как что:

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 260

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 когда нет матча. Но давайте не обращайте внимания на это незначительное раздражение. На самом деле ужасное стандартное решение сбой в этом случае и я считаю, что возвращаю индекс, который превосходит границы.

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

анализ

плюсы

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

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

минусы

  • не совсем очевидно на первый
  • тут не вернуть -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;
}