如何使用LINQ获取索引?

给定一个这样的数据源:

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

我怎样才能find满足一定条件的第一辆汽车的指数

编辑:

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

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

用一个普通的旧循环来解决这个问题是否是最好的?

一个IEnumerable不是一个有序集合。
虽然大多数IEnumerables是有序的,但有些(如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)); } 
 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(); 

有用! 想想看。 第一个匹配项目的索引等于之前(不匹配)项目的数量。

讲故事的时间

我也不喜欢你的问题已经提出的可怕的标准解决scheme 。 就像接受的答案,我去了一个普通的旧循环,虽然稍作修改:

 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 。 但是现在让我们忽略这个小小的烦恼吧。 事实上, 可怕的标准解决scheme在这种情况下崩溃, 我认为返回一个超越界限的索引 。

现在发生的事情是ReSharper告诉我Loop可以转换成LINQexpression式 。 虽然大多数时候这个function会使可读性恶化,但是这一次的结果令人惊叹。 所以对JetBrains的荣誉。

分析

优点

  • 简洁
  • 与其他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; }