C# 合并两个集合中不同的项

7

我正在寻找一种高效的方法将第二个ICollection中不同的项添加到现有的ICollection中。我使用的是.NET 4。

4个回答

13
这应该可以实现:
list1.Union(list2).Distinct(aCustomComparer).ToList()

在使用Union()之后再使用Distinct()不是多余的吗? - 0xbadf00d
2
取决于联合的条件。默认情况下,Union() 方法只会去重相同引用的对象。由于 Distinct 使用了自定义的 IEqualityComparer,它实际上比联合要做更多的事情。但是,这个 IEqualityComparer 也可以直接提供给 Union 作为其去重器使用,因此它有点多余。 - KeithS
Union 使用默认的相等比较器,具体取决于类型是否为引用比较器。请参阅 https://msdn.microsoft.com/en-us/library/vstudio/bb341731(v=vs.100).aspx 和 https://msdn.microsoft.com/en-us/library/vstudio/bb341731(v=vs.100).aspx。此外,正如您所提到的,最好为 Union 方法提供自定义比较器,从而消除 Distinct 调用以提高可读性和(可能?)轻微的性能提升。 - Igor Pashchuk
"aCustomComparer"是什么? - Penjimon

6
只要它们是可枚举的,您就可以使用Linq答案:
var union = firstCollection.Union(secondCollection);

这将使用默认的相等比较,对于大多数对象来说是引用相等。要更改此设置,您可以为集合中的项类型定义一个IEqualityComparer通用类,执行更语义化的比较,并将其指定为Union的第二个参数。


2

另一种添加到您现有列表的方法是:

list1.AddRange(list2.Distinct().Except(list1));

这基本上是 Union() 的操作,顺便说一下。Except() 迭代器基本上会在传递的 list1 上调用 Any(x=>x==Current),并返回 list2 中 false 的项。 - KeithS
除了 Union 不修改原始列表外,OP 请求“将第二个 ICollection 的不同项添加到现有的一个” ,Union 返回一个新的集合。 - Patrick McDonald
我的回答可能不是 OP 想要的,但他的问题足够模糊,以至于我的回答也有可能是正确的。 - Patrick McDonald

2
您的问题的最直接答案 - 因为您没有详细说明实际输入或输出所需的ICollection类型 - 是由KeithS提供的答案。
var union = firstCollection.Union(secondCollection);

这将返回一个不同的IEnumerable - 如果这是您需要的,那么它非常快。我制作了一个小测试应用程序(如下),运行union方法(MethodA)针对简单的哈希集方法进行去重并返回Hashset<>(MethodB)。联合方法毁掉了哈希集:

MethodA:1ms

MethodB:2827ms

然而 - 必须将该IEnumerable转换为其他类型的集合,例如List<>(如ADas发布的版本)会改变一切:

只需在MethodA中添加.ToList()即可。

var union = firstCollection.Union(secondCollection).ToList();

改变结果:

方法A:3656毫秒

方法B:2803毫秒

所以,似乎需要更多了解你所处理的具体情况,并且任何解决方案都应该经过测试,因为一个小的(代码)改变可能会产生巨大的影响。

以下是我用来比较这些方法的测试 - 我确定这是一种愚蠢的测试方式 - 但它似乎有效 :)

    private static void Main(string[] args)
    {
        ICollection<string> collectionA = new List<string>();
        ICollection<string> collectionB = new List<string>();
        for (int i = 0; i < 1000; i++)
        {
            string randomString = Path.GetRandomFileName();
            collectionA.Add(randomString);
            collectionA.Add(randomString);
            collectionB.Add(randomString);
            collectionB.Add(randomString);
        }
        Stopwatch testA = new Stopwatch();
        testA.Start();
        MethodA(collectionA, collectionB);
        testA.Stop();


        Stopwatch testB = new Stopwatch();
        testB.Start();
        MethodB(collectionA, collectionB);
        testB.Stop();

        Console.WriteLine("MethodA: {0}ms", testA.ElapsedMilliseconds);
        Console.WriteLine("MethodB: {0}ms", testB.ElapsedMilliseconds);
        Console.ReadLine();
    }

    private static void MethodA(ICollection<string> collectionA, ICollection<string> collectionB)
    {
        for (int i = 0; i < 10000; i++)
        {
            var result = collectionA.Union(collectionB);
        }
    }

    private static void MethodB(ICollection<string> collectionA, ICollection<string> collectionB)
    {
        for (int i = 0; i < 10000; i++)
        {
            var result = new HashSet<string>(collectionA);
            foreach (string s in collectionB)
            {
                result.Add(s);
            }
        }
    }

只是为了明确一点; 总有比Linq更高效的解决方案。然而,对于你在.NET中编程的99.9%的情况(与更本地的语言相比,它们会使任何.NET实现都变得不堪一击),Linq在性能上是可以接受的,并且通常更易读和理解。 - KeithS
MethodA 之所以快得多,是因为它不枚举返回的集合元素,这就是调用 ToList 使其变慢的原因。 - Patrick McDonald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接