如何使用 LINQ 获取索引? [复制]

Posted

技术标签:

【中文标题】如何使用 LINQ 获取索引? [复制]【英文标题】:How to get index using LINQ? [duplicate] 【发布时间】:2010-03-18 16:30:47 【问题描述】:

给定这样的数据源:

var c = new Car[]

  new Car Color="Blue", Price=28000,
  new Car Color="Red", Price=54000,
  new Car Color="Pink", Price=9999,
  // ..
;

如何使用 LINQ 找到满足特定条件的第一辆车的 index

编辑:

我能想到这样的事情,但看起来很可怕:

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

用普通的旧循环解决这个问题最好吗?

【问题讨论】:

类似:get-list-element-position-in-c-sharp-using-linq 即使这些信息也会有所帮助 - ***.com/questions/4049773/… 其实有一个index声明:var result = items.Select((item, index) => new index, item ); 【参考方案1】:
myCars.Select((v, i) => new car = v, index = i).First(myCondition).index;

或者稍微短一点的

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

或略短的更短

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

【讨论】:

我刚用过它,它对我来说很好用。这与标记的答案有何不同? 最大的区别在于,如果任何项目都不满足 myCondition 条件,并且在这种情况下标记的答案返回 -1,这将引发异常。 @ProfK 小心 FirstOrDefault,默认为 null 的类,在 null 上调用 .index 并抛出异常。 @YuriyFaktorovich 我不明白它如何知道将“v”与汽车对象和“i”关联起来作为索引。我看到了“car = v, index = i”,但是运行时系统如何知道你的意思是你想要 v 中的汽车对象和 i 中的数组中的索引? 只是为未来的搜索者评论说,C#6 将允许myCars.Select((car, index) => new car, index).FirstOrDefault(myCondition)?.index; 在处理应用 myCondition 后没有结果的情况时返回空索引。【参考方案2】:

简单地做:

int index = List.FindIndex(your condition);

例如

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

【讨论】:

+1 - 虽然 LINQ 只处理 IEnumerable,但这个答案让我意识到,在我的情况下,可以将 IEnumerable 转换为 list 然后调用 FindIndex 对于数组只需使用Array.FindIndex @beluchin 请记住,如果您将IEnumerable 转换为ListIEnumerable 就不再是懒惰了。你强制获取它的 Every 元素,即使你实际上并不需要它们。 这是一个完美的解决方案,只要您使用独特的条件。在其他情况下,当多个元素可能匹配时,您将不会获得索引列表,而只会获得其中的第一个元素。【参考方案3】:

IEnumerable 不是有序集。 尽管大多数 IEnumerable 是有序的,但有些(例如 DictionaryHashSet)不是。

因此,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)); 

【讨论】:

虽然它确实有一个 ElementAt 方法。它将索引作为参数。 因为这是所有其他 LINQ 方法所使用的。它使工具提示中的委托签名更清晰。 PredicateComparison 和朋友在 .Net 3.5 中被 Func 代表有效地取代。 @280Z28: LINQ 已经与 List&lt;T&gt; - FindAll(Predicate&lt;T&gt;)Where(Func&lt;T, bool&gt;)Exists(Predicate&lt;T&gt;)Any(Func&lt;T, bool&gt;)ConvertAll(Converter&lt;T, TOutput&gt;)Select(Func&lt;T1, T2&gt;) 等不一致。 @SLaks:对于所有其他依赖顺序的方法(ElementAt、First、Last、Skip 和朋友),我认为 IndexOf 不会太牵强。 第一位令人困惑,应该改写/删除。 IEnumerable 公开了一个 IEnumerator,它有两个成员:MoveNext()Current - 它本质上是有序的。【参考方案4】:
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。但是,让我们暂时忽略这个小烦恼。事实上,可怕的标准解决方案在这种情况下会崩溃,I consider returning an index that is out-of-bounds superior。

现在发生的事情是 ReSharper 告诉我 Loop can be converted into LINQ-expression。虽然大多数时候该功能会降低可读性,但这一次的结果令人敬畏。向 JetBrains 致敬。

分析

优点

简洁 可与其他 LINQ 结合 避免newing 匿名对象 只计算可枚举,直到谓词第一次匹配

因此,我认为它在时间和空间上都是最佳的,同时保持可读性。

缺点

一开始不太明显 没有匹配时不返回-1

当然,您始终可以将其隐藏在扩展方法后面。而在没有匹配的情况下如何做最好在很大程度上取决于上下文。

【讨论】:

在循环之前添加简单的isFound布尔值,在break语句之前将此变量设置为true并在从函数返回之前与它进行比较将解决-1问题 我实际上需要一个 LINQ 查询,如果没有找到项目,它将返回完整计数,所以这是完美的! return index 而不是 break 内部循环将保留功能和可读性,并且如果没有找到元素,则可以轻松地将最后一个 return 转换为返回 -1。【参考方案5】:

我会在这里做出我的贡献...为什么?只是因为 :p 它是基于 Any 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();
        

【讨论】:

顺便说一下,代码可以在LINQPad上运行【参考方案6】:

这是我刚刚整理的一个小扩展。

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"));

【讨论】:

【参考方案7】:

这是最高投票答案的实现,当找不到该项目时返回 -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;

【讨论】:

只使用 DefaultIfEmpty(-1) @TimSchmelter,谢谢;我不认为我以前使用过这种方法。它比我在这里做的方式更好吗?你觉得它更具可读性吗?

以上是关于如何使用 LINQ 获取索引? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 LINQ 从列表中获取重复项? [复制]

如何使用 LINQ 在 asp.net mvc 中获取具有特定子值的所有父母? [复制]

使用 Linq 从列表中获取所有匹配值的索引

连续获取最大值时如何解决错误(EF Core Linq)? [复制]

如何获取 Linq 查询结果集合中的索引?

如何使用 LINQ 从列表中选择提供的索引范围内的值