如何使用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是有序的,但有些(如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();
有用! 想想看。 第一个匹配项目的索引等于之前(不匹配)项目的数量。
讲故事的时间
我也不喜欢你的问题已经提出的可怕的标准解决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; }