【问题标题】:Elegant way to combine multiple collections of elements?结合多个元素集合的优雅方式?
【发布时间】:2011-05-28 11:44:44
【问题描述】:

假设我有任意数量的集合,每个集合都包含相同类型的对象(例如,List<int> fooList<int> bar)。如果这些集合本身在一个集合中(例如,List<List<int>> 类型,我可以使用 SelectMany 将它们全部组合到一个集合中。

但是,如果这些集合不在同一个集合中,我的印象是我必须编写这样的方法:

public static IEnumerable<T> Combine<T>(params ICollection<T>[] toCombine)
{
   return toCombine.SelectMany(x => x);
}

然后我会这样称呼:

var combined = Combine(foo, bar);

有没有一种简洁、优雅的方式来组合(任意数量的)集合,而不必编写像上面的 Combine 这样的实用方法?看起来很简单,在 LINQ 中应该有一种方法可以做到这一点,但也许没有。

【问题讨论】:

标签: c# linq generics collections


【解决方案1】:

使用Enumerable.Concat:

var query = foo.Concat(bar);

【讨论】:

    【解决方案2】:

    使用集合初始化器的几种技术 --

    假设这些列表:

    var list1 = new List<int> { 1, 2, 3 };
    var list2 = new List<int> { 4, 5, 6 };
    

    SelectMany 带有一个数组初始化器(对我来说并不是那么优雅,但不依赖任何辅助函数):

    var combined = new []{ list1, list2 }.SelectMany(x => x);
    

    为 Add 定义一个列表扩展名,允许 IEnumerable&lt;T&gt;List&lt;T&gt; initializer 中:

    public static class CollectionExtensions
    {
        public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
        {
            foreach (var item in items) collection.Add(item);
        }
    }
    

    然后您可以像这样创建一个包含其他元素的新列表(它还允许混合单个项目)。

    var combined = new List<int> { list1, list2, 7, 8 };
    

    【讨论】:

      【解决方案3】:

      如果您确实拥有一个集合集合,即List&lt;List&lt;T&gt;&gt;Enumerable.Aggregate 是将所有列表合并为一个的更优雅的方式:

      var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });
      

      【讨论】:

      • 您如何首先在此处获得lists?这是 OP 的第一个问题。如果他有一个collection&lt;collection&gt; 开头,那么SelectMany 就简单多了。
      • 不确定发生了什么,但这似乎回答了错误的问题。编辑答案以反映这一点。很多人似乎确实到了这里,因为需要结合 List>
      【解决方案4】:

      对我来说,Concat 作为扩展方法在我的代码中不是很优雅,因为我有多个要连接的大序列。这只是一个代码缩进/格式问题,而且非常个人化。

      当然看起来不错:

      var list = list1.Concat(list2).Concat(list3);
      

      读起来不那么可读:

      var list = list1.Select(x = > x)
         .Concat(list2.Where(x => true)
         .Concat(list3.OrderBy(x => x));
      

      或者当它看起来像:

      return Normalize(list1, a, b)
          .Concat(Normalize(list2, b, c))
             .Concat(Normalize(list3, c, d));
      

      或任何您喜欢的格式。更复杂的连接会变得更糟。我对上述风格的某种认知失调的原因是第一个序列位于Concat 方法之外,而随后的序列位于内部。我更喜欢直接调用静态Concat 方法而不是扩展样式:

      var list = Enumerable.Concat(list1.Select(x => x),
                                   list2.Where(x => true));
      

      对于更多的序列连接,我使用与 OP 中相同的静态方法:

      public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] sequences)
      {
          return sequences.SelectMany(x => x);
      }
      

      所以我可以写:

      return EnumerableEx.Concat
      (
          list1.Select(x = > x),
          list2.Where(x => true),
          list3.OrderBy(x => x)
      );
      

      看起来更好。考虑到我的序列通过Concat 调用看起来更干净,我必须编写的额外的、否则是多余的类名对我来说不是问题。这在 C# 6 中问题不大。你可以写:

      return Concat(list1.Select(x = > x),
                    list2.Where(x => true),
                    list3.OrderBy(x => x));
      

      希望我们在 C# 中有列表连接运算符,例如:

      list1 @ list2 // F#
      list1 ++ list2 // Scala
      

      这样更干净。

      【讨论】:

      • 感谢您对编码风格的关注。这更优雅。
      • 也许您的答案的较小版本可以与此处的最佳答案合并。
      • 我也喜欢这种格式——尽管为了清晰起见,我更喜欢先执行单个列表操作(例如WhereOrderBy),尤其是当它们比这个例子更复杂时。
      【解决方案5】:

      对于任何IEnumerable&lt;IEnumerable&lt;T&gt;&gt; lists,您只需要这个:

      var combined = lists.Aggregate((l1, l2) => l1.Concat(l2));
      

      这会将lists 中的所有项目合并为一个IEnumerable&lt;T&gt;(有重复项)。如其他答案所述,使用 Union 而不是 Concat 删除重复项。

      【讨论】:

        【解决方案6】:

        您始终可以将 Aggregate 与 Concat 结合使用...

                var listOfLists = new List<List<int>>
                {
                    new List<int> {1, 2, 3, 4},
                    new List<int> {5, 6, 7, 8},
                    new List<int> {9, 10}
                };
        
                IEnumerable<int> combined = new List<int>();
                combined = listOfLists.Aggregate(combined, (current, list) => current.Concat(list)).ToList();
        

        【讨论】:

        • 迄今为止最好的解决方案。
        • @Oleg SelectMany 更简单。
        【解决方案7】:

        我想您可能正在寻找 LINQ 的 .Concat()

        var combined = foo.Concat(bar).Concat(foobar).Concat(...);
        

        或者,.Union() 将删除重复元素。

        【讨论】:

        • 谢谢;我一开始没有这样做的唯一原因是(对我来说)你必须处理的收藏越多,它似乎就越难看。然而,这具有使用现有 LINQ 函数的好处,未来的开发人员可能已经熟悉这些函数。
        • 感谢.Union() 提示。请记住,您将需要在您的自定义类型上实现 IComparer,以防万一。
        【解决方案8】:

        鉴于您从一堆单独的集合开始,我认为您的解决方案相当优雅。您将不得不做一些事情将它们缝合在一起。

        从您的 Combine 方法中创建一个扩展方法在语法上会更方便,这样您可以随时随地使用它。

        【讨论】:

        • 我喜欢扩展方法的想法,我只是不想重新发明轮子,如果 LINQ 已经有一个可以做同样事情的方法(并且可以立即被其他开发人员理解) )
        【解决方案9】:

        像这样使用Enumerable.Concat

        var combined = foo.Concat(bar).Concat(baz)....;
        

        【讨论】:

          【解决方案10】:

          我看到的唯一方法是使用Concat()

           var foo = new List<int> { 1, 2, 3 };
           var bar = new List<int> { 4, 5, 6 };
           var tor = new List<int> { 7, 8, 9 };
          
           var result = foo.Concat(bar).Concat(tor);
          

          但你应该决定什么更好:

          var result = Combine(foo, bar, tor);
          

          var result = foo.Concat(bar).Concat(tor);
          

          有一点为什么Concat() 会是一个更好的选择,这对于其他开发人员来说会更加明显。更具可读性和简单性。

          【讨论】:

            【解决方案11】:

            您可以按如下方式使用联合:

            var combined=foo.Union(bar).Union(baz)...
            

            不过,这将删除相同的元素,因此如果您有这些元素,您可能希望使用 Concat 来代替。

            【讨论】:

            • 不! Enumerable.Union 是一个集合并集,它不会产生所需的结果(它只会产生一次重复)。
            • 是的,我需要对象的具体实例,因为我需要对它们进行一些特殊处理。在我的例子中使用int 可能会让人们有点失望;但Union 在这种情况下对我不起作用。
            猜你喜欢
            • 1970-01-01
            • 2023-04-11
            • 1970-01-01
            • 1970-01-01
            • 2011-10-28
            • 2013-02-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多