【问题标题】:LINQ to find array indexes of a valueLINQ查找值的数组索引
【发布时间】:2012-11-08 15:12:47
【问题描述】:

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

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

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

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

的列表

2、4

表示“avg”可以在 str[2] 和 str[4] 处找到。

【问题讨论】:

    标签: c# linq


    【解决方案1】:

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

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

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

    Documentation here

    【讨论】:

    • 请注意,Where 也提供了重载。
    • 不知道你在这里做了什么,但它确实有效。谢谢。
    【解决方案2】:

    你可以这样做:

    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 为您的函子提供当前索引。

    【讨论】:

    • 每一行的cmets都很棒!
    【解决方案3】:

    您可以使用传递索引的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");
    

    (或者您可以使用this overload 以开始索引来指定开始位置)

    【讨论】:

      【解决方案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,后者将它们存储在列表中.所有 LINQ 方法都经过精心设计,只对源序列进行一次迭代(当它们被迭代一次时)。

      请注意,虽然这对您来说一开始看起来很复杂,但实际上计算机可以轻松完成所有这些工作。它实际上并不像最初看起来那样需要大量性能。

      【讨论】:

        【解决方案5】:

        虽然您可以使用 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++;
            }
        }
        

        【讨论】:

        • 不错的选择。我认为空的itemToFind 应该是一个合法的用例,但对不同的东西有投票权。
        【解决方案6】:

        您需要一个组合的 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);
                    }
                }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-03-08
          • 1970-01-01
          • 1970-01-01
          • 2020-12-26
          • 2019-07-05
          • 2017-08-13
          • 1970-01-01
          相关资源
          最近更新 更多