【问题标题】:How to get index using LINQ? [duplicate]如何使用 LINQ 获取索引? [复制]
【发布时间】: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;

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

【问题讨论】:

标签: c# .net linq c#-3.0


【解决方案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,谢谢;我不认为我以前使用过这种方法。它比我在这里做的方式更好吗?你觉得它更具可读性吗?
    猜你喜欢
    • 2011-04-14
    • 2019-01-20
    • 2020-11-07
    • 1970-01-01
    • 2018-03-18
    • 2019-10-31
    • 1970-01-01
    相关资源
    最近更新 更多