如何使用 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
转换为List
,IEnumerable
就不再是懒惰了。你强制获取它的 Every 元素,即使你实际上并不需要它们。
这是一个完美的解决方案,只要您使用独特的条件。在其他情况下,当多个元素可能匹配时,您将不会获得索引列表,而只会获得其中的第一个元素。【参考方案3】:
IEnumerable
不是有序集。
尽管大多数 IEnumerable 是有序的,但有些(例如 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));
【讨论】:
虽然它确实有一个 ElementAt 方法。它将索引作为参数。 因为这是所有其他 LINQ 方法所使用的。它使工具提示中的委托签名更清晰。Predicate
、Comparison
和朋友在 .Net 3.5 中被 Func
代表有效地取代。
@280Z28: LINQ 已经与 List<T>
- FindAll(Predicate<T>)
与 Where(Func<T, bool>)
、Exists(Predicate<T>)
与 Any(Func<T, bool>)
、ConvertAll(Converter<T, TOutput>)
与 Select(Func<T1, T2>)
等不一致。
@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 结合 避免new
ing 匿名对象
只计算可枚举,直到谓词第一次匹配
因此,我认为它在时间和空间上都是最佳的,同时保持可读性。
缺点
一开始不太明显 没有匹配时不返回-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 在 asp.net mvc 中获取具有特定子值的所有父母? [复制]