【问题标题】:Getting a collection of index values using a LINQ query使用 LINQ 查询获取索引值的集合
【发布时间】:2010-09-19 05:43:51
【问题描述】:

有没有更好的方法来做到这一点?

string[] s = {"zero", "one", "two", "three", "four", "five"};

var x = 
s
.Select((a,i) => new {Value = a, Index = i})
.Where(b => b.Value.StartsWith("t"))
.Select(c => c.Index);

即我正在寻找一种更有效或更优雅的方式来获取符合条件的项目的位置。

【问题讨论】:

    标签: c# .net arrays linq indexof


    【解决方案1】:

    您可以轻松添加自己的扩展方法:

    public static IEnumerable<int> IndexesWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        int index=0;
        foreach (T element in source)
        {
            if (predicate(element))
            {
                yield return index;
            }
            index++;
        }
    }
    

    然后使用它:

    string[] s = {"zero", "one", "two", "three", "four", "five"};
    var x = s.IndexesWhere(t => t.StartsWith("t"));
    

    【讨论】:

      【解决方案2】:

      如果您只是将示例用作学习 LINQ 的一种方式,请忽略此帖子。


      我不清楚 LINQ 实际上是做到这一点的最佳方式。下面的代码似乎更有效,因为不需要创建新的匿名类型。当然,您的示例可能是人为的,并且该技术可能在不同的上下文中更有用,例如在可以利用值索引的数据结构中,但是下面的代码相当简单,可以理解(没有想过必需)并且可以说更有效。

      string[] s = {"zero", "one", "two", "three", "four", "five"};
      List<int> matchingIndices = new List<int>();
      
      for (int i = 0; i < s.Length; ++i) 
      {
         if (s[i].StartWith("t"))
         {
            matchingIndices.Add(i);
         }
      }
      

      【讨论】:

      • 感谢您的回答。我同意这会更有效率。正如您所猜测的那样,这是在这种特殊情况下“必须”在 LINQ 中完成的更复杂的事情的简化版本。
      • 只有当您使用 List 而不是任意 IEnumerable 时,该确切代码才有效。否则你必须使用 foreach 和一个单独的局部变量。
      【解决方案3】:

      对我来说似乎很好。您可以通过将选择更改为:

      .Select((Value, Index) => new {Value, Index})
      

      【讨论】:

      • 谢谢 - 我不知道你能做到 - 我以为你必须重新分配。
      • 它被称为“投影初始化器”——它基本上采用表达式中的最后一个子表达式(必须是字段或属性)并将其用作名称。所以你可以做 x.GetFoo().Bar ,这相当于 Bar=x.GetFoo().Bar。
      【解决方案4】:

      Collection List 中还有 FindIndex 方法,您可以为其创建一个删除方法,该方法可以从集合中返回索引。可以参考msdnhttp://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx下面的链接。

      【讨论】:

        【解决方案5】:

        这个怎么样?它类似于原始海报,但我首先选择索引,然后构建一个符合条件的集合。

        var x = s.Select((a, i) => i).Where(i => s[i].StartsWith("t"));
        

        这比其他一些答案的效率要低一些,因为列表完全迭代了两次。

        【讨论】:

          【解决方案6】:

          我和一位同事讨论了这个有趣的问题,起初我认为 JonSkeet 的解决方案很棒,但我的同事指出了一个问题,即如果该函数是 IEnumerable&lt;T&gt; 的扩展,那么它可以用于集合实现它。

          对于数组,可以肯定地说使用foreach 生成的顺序将得到尊重(即foreach 将从第一个到最后一个迭代),但对于其他集合(列表、字典、等),其中foreach 不会反映必然“进入顺序”。然而该功能是存在的,它可能会产生误导。

          最后,我得到了类似于 tvanfosson 的答案,但作为数组的扩展方法:

          public static int[] GetIndexes<T>(this T[]source, Func<T, bool> predicate)
          {
              List<int> matchingIndexes = new List<int>();
          
              for (int i = 0; i < source.Length; ++i) 
              {
                  if (predicate(source[i]))
                  {
                      matchingIndexes.Add(i);
                  }
              }
              return matchingIndexes.ToArray();
          }
          

          希望List.ToArray能尊重最后一次操作的顺序……

          【讨论】:

          • “但功能就在那里,它可能会产生误导。” - 但这对于任何其他使用索引的 LINQ 方法都是一样的,例如.Select((item, index) =&gt; 。只要你明白你得到了什么,我认为 Jon 的功能不一定有什么问题。
          • @Rup 自从那个答案以来,我已经收集了更多使用 LINQ 和 IEnumerable 接口的经验。我同意,如果您知道自己在做什么,并且定义了函数,就不会有意外。一个潜在的危险是从使用AsParallel 生成的IEnumerable 中获取索引。这些索引将反映集合的生成状态,但再次生成它几乎可以保证给出不同的结果。索引将引用不存在的东西。在团队合作中(或稍后重用该功能),如果该行为不明确,您将遇到麻烦。
          • 我仍然认为除非您修改集合,否则顺序不太可能改变,并且您不应该使用索引,除非它对结构有意义。但我没有考虑过 AsParallel - 整洁,谢谢。
          猜你喜欢
          • 2019-07-11
          • 1970-01-01
          • 2016-06-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-05
          相关资源
          最近更新 更多