嵌套与链式联盟

时间:2018-03-26 14:24:47

标签: performance linq ienumerable

从逻辑上讲,以下内容完全相同:

var foo = (  A.Union(B).Union(C).Union(D)  ).ToList();
var bar = (  A.Union(B.Union(C.Union(D)))  ).ToList();
var baz = (  D.Union(C.Union(B.Union(A)))  ).ToList();

他们应该在最后返回完全相同的列表。

它们之间有什么区别(如果有的话)?

我猜想唯一的区别是与您在每个集合中迭代的频率相关的性能问题?那么foobaz具有完全相同的效果 - 迭代A 4次,但仅超过D一次?

是吗?

是否有任何其他有趣的属性可能会引导您关注做一个而不是另一个?

2 个答案:

答案 0 :(得分:5)

这些解决方案都没有多次迭代它的参数。此外,参数按照文本中给出的顺序进行迭代,ABCD用于foo和{{1} } {},barDCB A

您可以使用一个简单的生成器来演示这一点,该生成器会在您迭代时打印它返回的项目:

baz

Demo.

多次枚举集合的原因是class VisibleIterator : IEnumerable<string> { private readonly string name; public VisibleIterator(string name) { this.name = name; } public IEnumerator<string> GetEnumerator() { for (var i = 0 ; i != 4 ; i++) { var res = name+i; Console.WriteLine(res); yield return res; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ({1}}后面的代码保留了已访问过的项的哈希集:

UnionIterator<T>

Union<T>static IEnumerable<TSource> UnionIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer) { Set<TSource> set = new Set<TSource>(comparer); foreach (TSource element in first) if (set.Add(element)) yield return element; foreach (TSource element in second) if (set.Add(element)) yield return element; } 的大小可能会导致性能差异很小。每个示例中都会有三个这样的集合 - 每个Set<TSource> set调用一个。顶级UnionIterator<T>始终以Union结果的所有成员结束。但是,中级set可能包含更多项目或更少项目,具体取决于您合并馆藏的顺序以及每个馆藏中相对项目的数量。

答案 1 :(得分:1)

虽然dasblinkenlight是正确的,每个项目都只迭代一次,但三个版本可能仍然具有可衡量的性能差异,具体取决于您的对象。

这些项目将被插入到不同数量的Hashsets中,具体取决于它们在Union树下的距离。

虽然插入Hashset名义上是O(1),但它确实有成本,而且在实践中并不总是不变的,具体取决于对象的细节。

当项目被插入Hashset时,会调用GetHashCode,并且需要使用Equals将项目与集合中具有相同int哈希码的任何其他对象进行比较。对于极其复杂的对象,GetHashCode可能很昂贵。如果项目hashkeys没有广泛分发,则可能会调用Equals,这可能很昂贵。

以下演示基于@dasblinkenlight的回答显示GetHashCode被调用的次数不同,具体取决于Union排序。在哈希冲突的情况下,我没有演示Equals被调用,但如果你愿意,你可以尝试一下。

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

public class Test {
    public static void Main() {
            var A = new VisibleIterator("A");
            var B = new VisibleIterator("B");
            var C = new VisibleIterator("C");
            var D = new VisibleIterator("D");
            Console.WriteLine("--- A.Union(B).Union(C).Union(D)");
            var foo = (A.Union(B).Union(C).Union(D)).ToList();
            Console.WriteLine("--- A.Union(B.Union(C.Union(D)))");
            var bar = (A.Union(B.Union(C.Union(D)))).ToList();
            Console.WriteLine("--- D.Union(C.Union(B.Union(A)))");
            var baz = (D.Union(C.Union(B.Union(A)))).ToList();
    }
}

    class VisibleIterator : IEnumerable<VisibleHasher> {
        private readonly string name;
        public VisibleIterator(string name) {
            this.name = name;
        }
        public IEnumerator<VisibleHasher> GetEnumerator() {
            for (var i = 0 ; i != 4 ; i++) {
                var res = name+i;
                Console.WriteLine("Iterating " + res);
                yield return new VisibleHasher(res);
            }
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }

    class VisibleHasher {
        private readonly string val;

        public VisibleHasher(String val) {
            this.val = val;
        }

        public override int GetHashCode() {
            Console.WriteLine("Hashing '" + val + "'");
            return val.GetHashCode();
        }
    }

Demo(基于dasblinkenlight&#39;答案)

替代方法

如果您认为这些哈希插入的成本可能很高,那么以下内容应该保证每个项目有一个哈希插入:

A.Concat(B).Concat(C).Concat(D).Distinct().ToList()
相关问题