结合多个元素集合的优雅方式?


94

假设我有任意数量的集合,每个集合都包含相同类型的对象(例如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中做到这一点,但也许不是。



谨防Concat串联大量枚举,可能会炸毁您的堆栈:programmaticallyspeaking.com/…–
nawfal,

Answers:


107

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

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

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


2
谢谢; 我之所以没有这样做的唯一原因是(对我而言)您要处理的收藏越多越难看。但是,这样做的好处是可以使用现有的LINQ功能,将来的开发人员可能已经很熟悉这些功能。
甜甜圈2010年

2
感谢您的.Union()提示。请记住,您将需要对自定义类型实现IComparer。
Gonzo345

29

对我来说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直接调用static 方法,而不是扩展样式:

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

这样更干净。


4
感谢您对编码风格的关注。这要优雅得多。
Bryan Rayner

也许您的答案的较小版本可以与此处的最高答案合并。
布莱恩·雷纳

2
我喜欢这个格式,以及-尽管我更喜欢进行单独的列表操作(例如WhereOrderBy第一)为清楚起见,特别是如果他们是什么比这个例子更复杂。
brichins

27

对于您确实有一个集合的集合(即)的情况List<List<T>>,这Enumerable.Aggregate是将所有列表合并为一个的更优雅的方法:

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

1
您如何首先到达lists这里?这是OP的第一个问题。如果他有一个collection<collection>开始,那就SelectMany更简单了。
nawfal

不知道发生了什么,但这似乎是在回答错误的问题。编辑答案以反映这一点。许多人似乎确实会因为在这里合并List <List <T >>
astreltsov,



7

您可以始终将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();

1
迄今为止最好的解决方案。
奥列格(Oleg)'18

@Oleg SelectMany更简单。
nawfal

6

我看到的唯一方法是使用 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()会是更好的选择是,这将是另一个开发者更为明显。更具可读性和简单性。


3

您可以按如下方式使用Union:

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

但是,这将删除相同的元素,因此,如果有这些元素,则可能需要使用Concat


4
没有!Enumerable.Union是一个集合联合,不会产生期望的结果(它只会产生重复项一次)。
杰森2010年

是的,我需要对象的特定实例,因为我需要对它们进行一些特殊处理。int在我的示例中,使用可能会使人们有点失望。但Union在这种情况下对我不起作用。
甜甜圈2010年

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<T>List<T> 初始化程序中使用

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 };

1

鉴于您是从一堆单独的收藏开始的,我认为您的解决方案非常优雅。你将不得不做点什么将它们缝合在一起。

从语法上来说,从您的Combine方法中扩展一个扩展方法会更加方便,这将使您可以在任何地方使用它。


我喜欢的扩展方法的想法,我只是不希望(立即是可以理解的其他开发人员进一步向下行)推倒重来,如果LINQ已经有了,会做同样的事情的方法
甜甜圈

1

您需要的是所有这些IEnumerable<IEnumerable<T>> lists

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.