LINQfind一个值的数组索引
假设我有以下string数组:
string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}
使用LINQ获取匹配一个string的索引列表是否可行?
作为一个例子,我想searchstring“avg”并得到一个包含的列表
2,4
这意味着“平均”可以在str [2]和str [4]find。
.Select
有一个很less使用的产生索引的重载。 你可以像这样使用它:
str.Select((s, i) => new {i, s}) .Where(t => ts == "avg") .Select(t => ti) .ToList()
结果将是一个包含2和4的列表。
文档在这里
你可以这样做:
str.Select((v,i) => new {Index = i, Value = v}) // Pair up values and indexes .Where(p => p.Value == "avg") // Do the filtering .Select(p => p.Index); // Keep the index and drop the value
关键的一步是使用Select
的重载,将当前索引提供给函子。
您可以使用传递索引的Enumerable.Select
的重载,然后在匿名types上使用Enumerable.Where
:
List<int> result = str.Select((s, index) => new { s, index }) .Where(x => xs== "avg") .Select(x => x.index) .ToList();
如果你只是想find第一个/最后一个索引,你也有内build方法List.IndexOf
和List.LastIndexOf
:
int firstIndex = str.IndexOf("avg"); int lastIndex = str.LastIndexOf("avg");
(或者你可以使用这个重载 ,以开始索引来指定开始位置)
虽然您可以使用Select
和Where
的组合,但这可能是您创build自己的function的好select:
public static IEnumerable<int> Indexes<T>(IEnumerable<T> source, T itemToFind) { if (source == null) throw new ArgumentNullException("source"); int i = 0; foreach (T item in source) { if (object.Equals(itemToFind, item)) { yield return i; } i++; } }
首先,你的代码实际上并不是遍历列表两次,它只是迭代一次。
也就是说,你的Select只是获取所有索引的序列。 用Enumerable.Range更容易完成:
var result = Enumerable.Range(0, str.Count) .Where(i => str[i] == "avg") .ToList();
了解列表为什么不实际迭代两次将需要一些习惯。 我会尽量给出一个基本的解释。
你应该考虑大部分的LINQ方法,比如Select和Where作为pipe道。 每种方法都做了一些小小的工作。 在select的情况下,你给它一个方法,它基本上说,“每当有人问我的下一个项目,我会先问我的input序列的一个项目,然后使用我必须将其转换成其他的方法,然后把这个物品交给谁来使用我。“ 或多或less地说,“每当有人问我一个项目,我会问我的input序列的一个项目,如果函数说,这是好的,我会通过它,如果不是,我会一直要求项目直到我得到一个通过。“
所以,当你链接他们会发生什么是ToList要求的第一个项目,它去哪里,因为它的第一个项目,去哪里select,并要求它的第一个项目,select进入列表要求它的第一个项目。 列表然后提供它的第一个项目。 然后select将该项目转换为需要吐出的项目(在这种情况下,只是int 0)并将其提供给Where。 哪里拿这个项目,运行它的function,确定它是真实的,所以吐出0 ToList,它将其添加到列表。 那整个事情再发生9次。 这意味着Select将最终要求从列表中的每个项目只需要一次,并将其每个结果直接提供给Where,这会将“通过testing”的结果直接提供给ToList,将它们存储在列表中。 所有的LINQ方法都经过精心devise,只需要迭代一次源代码(迭代一次)。
请注意,虽然这在起初对你来说看起来很复杂,但对于计算机来说,实现这一切其实很简单。 这实际上并不像表面上看起来那样严格。
你需要一个合并的select和where操作符,比较接受的答案会更便宜,因为不需要中间对象:
public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> filter, Func<TSource, int, TResult> selector) { int index = -1; foreach (var s in source) { checked{ ++index; } if (filter(s)) yield return selector(s, index); } }