66

假设我有以下字符串数组:

string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"}

是否可以使用 LINQ 获取与一个字符串匹配的索引列表?

例如,我想搜索字符串“avg”并获取一个包含

2、4

这意味着“avg”可以在 str[2] 和 str[4] 中找到。

4

6 回答 6

126

.Select有一个很少使用的产生索引的重载。你可以像这样使用它:

str.Select((s, i) => new {i, s})
    .Where(t => t.s == "avg")
    .Select(t => t.i)
    .ToList()

结果将是一个包含 2 和 4 的列表。

文档在这里

于 2012-11-08T15:15:57.053 回答
23

你可以这样做:

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

于 2012-11-08T15:16:38.317 回答
7

您可以使用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.IndexOfList.LastIndexOf

int firstIndex = str.IndexOf("avg");
int lastIndex = str.LastIndexOf("avg");

(或者您可以使用带有起始索引的此重载来指定起始位置)

于 2012-11-08T15:18:43.830 回答
4

首先,您的代码实际上并没有迭代列表两次,它只迭代一次。

也就是说,您的 Select 实际上只是获取所有索引的序列;使用 Enumerable.Range 更容易做到这一点:

 var result = Enumerable.Range(0, str.Count)
                 .Where(i => str[i] == "avg")
                 .ToList();

理解为什么列表实际上没有迭代两次需要一些时间来适应。我将尝试给出一个基本的解释。

您应该将大多数 LINQ 方法(例如 Select 和 Where)视为管道。每种方法都做了一些微小的工作。在 Select 的情况下,你给它一个方法,它本质上是说,“每当有人问我下一个项目时,我会首先向我的输入序列询问一个项目,然后使用我必须将其转换为其他东西的方法,然后把那个东西交给任何使用我的人。” 或多或少是在说,“每当有人问我要一个项目时,我会向我的输入序列询问一个项目,如果函数说它很好,我会传递它,如果不是,我会继续要求项目直到我得到一个通过。”

因此,当您将它们链接起来时,会发生 ToList 询问第一项,它转到 Where to 作为它的第一项, Where 转到 Select 并询问它的第一项, Select 转到列表以询问它的第一项物品。然后列表提供它的第一项。Select 然后将该项目转换为它需要吐出的内容(在这种情况下,只是 int 0)并将其提供给 Where。Where 获取该项目并运行它的函数,该函数确定它是真的,因此向 ToList 吐出 0,它将它添加到列表中。然后整个事情又发生了9次。这意味着 Select 最终将只要求列表中的每个项目一次,并将其每个结果直接提供给 Where,后者会将“通过测试”的结果直接提供给 ToList,后者将它们存储在列表中.

Note that, while this seems complicated at first to you, it's actually pretty easy for the computer to do all of this. It's not actually as performance intensive as it may seem at first.

于 2014-10-29T14:02:11.457 回答
2

虽然您可以使用 和 的组合SelectWhere但这可能是制作您自己的函数的好选择:

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++;
    }
}
于 2012-11-08T15:23:57.607 回答
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);
            }
        }
于 2014-04-19T01:26:00.830 回答