假设我有以下字符串数组:
string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}
是否可以使用LINQ获取与一个字符串匹配的索引列表?
例如,我想搜索字符串“avg”并获取包含
的列表2,4
意思是“avg”可以在str [2]和str [4]中找到。
答案 0 :(得分:90)
.Select
有一个很少使用的重载,它会产生一个索引。您可以像这样使用它:
str.Select((s, i) => new {i, s})
.Where(t => t.s == "avg")
.Select(t => t.i)
.ToList()
结果将是一个包含2和4的列表。
答案 1 :(得分:14)
你可以这样做:
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
关键步骤是使用the overload of Select
为您的仿函数提供当前索引。
答案 2 :(得分:6)
您可以使用传递索引的Enumerable.Select
重载,然后在匿名类型上使用Enumerable.Where
:
List<int> result = str.Select((s, index) => new { s, index })
.Where(x => x.s== "avg")
.Select(x => x.index)
.ToList();
如果您只想查找第一个/最后一个索引,还有内置方法List.IndexOf
和List.LastIndexOf
:
int firstIndex = str.IndexOf("avg");
int lastIndex = str.LastIndexOf("avg");
(或者你可以使用this overload取一个起始索引来指定起始位置)
答案 3 :(得分:2)
虽然您可以使用Select
和Where
的组合,但这可能是制作您自己的功能的好选择:
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++;
}
}
答案 4 :(得分:1)
首先,您的代码实际上没有两次迭代列表,它只迭代一次。
那就是说,你的Select实际上只是得到了所有索引的序列;使用Enumerable.Range更容易完成:
var result = Enumerable.Range(0, str.Count)
.Where(i => str[i] == "avg")
.ToList();
理解为什么列表实际上没有被迭代两次将需要一些时间来习惯。我试着给出一个基本的解释。
您应该考虑大多数LINQ方法,例如Select和Where作为管道。每种方法都做了一些工作。在Select的情况下,你给它一个方法,它基本上说,&#34;每当有人问我下一个项目时,我首先会问我输入序列的项目,然后使用我必须转换的方法它变成了别的东西,然后把它交给任何使用我的人。&#34;无论在哪里,或多或少,都会说,&#34;每当有人问我一件物品时,我会问我的输入序列是否有物品,如果该功能说它好,我会通过它如果没有,我会一直询问物品,直到我得到一件物品。&#34;
所以,当你把他们链接起来时,ToList会要求第一个项目,它会转到它的第一个项目,去哪里选择并询问它的第一个项目,选择转到列表以询问其第一个项目。然后该列表提供了它的第一项。选择然后将该项转换为需要吐出的内容(在本例中,只是int 0)并将其转移到Where。在哪里获取该项并运行它的函数,该函数确定它是真的并且因此将0吐出到ToList,这将其添加到列表中。那整件事情又发生了9次。这意味着Select将最终要求列表中的每个项目恰好一次,并且它会将每个结果直接提供给Where,这将提供&#34;通过测试的结果&#34;直接到ToList,它将它们存储在列表中。所有LINQ方法都经过精心设计,只能迭代源序列一次(当它们被迭代一次时)。
请注意,虽然这对您来说似乎很复杂,但计算机实际上很容易完成所有这些操作。它实际上并不像最初看起来那样具有性能密集程度。
答案 5 :(得分:0)
你需要一个组合的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);
}
}