Performance Linq连接两个列表:使用字典是否更有效?

Performance Linq连接两个列表:使用字典是否更有效?,performance,linq,Performance,Linq,最后的重新措辞 下面我连接了两个序列,我想知道使用连接的keySelector作为键创建一个序列的字典,并遍历另一个集合并在字典中找到键是否会更快 这仅在键选择器唯一时有效。如果两个记录具有相同的密钥,则真正的联接没有问题。在字典中,你必须有唯一的键 我测量了差异,发现dictionary方法快了13%。在大多数用例中是可忽略的。看到我对这个问题的回答了吗 重新措辞的问题 有人建议这个问题与相同,但这个问题不是关于使用where或join,而是关于使用字典执行join 我的问题是:如果我想基于一

最后的重新措辞 下面我连接了两个序列,我想知道使用连接的keySelector作为键创建一个序列的字典,并遍历另一个集合并在字典中找到键是否会更快

这仅在键选择器唯一时有效。如果两个记录具有相同的密钥,则真正的联接没有问题。在字典中,你必须有唯一的键

我测量了差异,发现dictionary方法快了13%。在大多数用例中是可忽略的。看到我对这个问题的回答了吗

重新措辞的问题

有人建议这个问题与相同,但这个问题不是关于使用where或join,而是关于使用字典执行join

我的问题是:如果我想基于一个键选择器连接两个序列,哪种方法会更快

  • 将一个序列的所有项放入字典,然后枚举另一个序列,以查看该项是否在字典中。这意味着在两个序列中迭代一次,并在keySelector上为两个序列中的每个项计算一次哈希代码
  • 另一种方法:使用System.Enumerable.Join
  • 问题是:第一个列表中的每个元素的Enumerable.Join会根据键选择器迭代第二个列表中的元素以查找匹配项,必须比较N*N个元素(这称为二阶吗?),还是使用更高级的方法


    带示例的原始问题

    我有两个类,都有一个属性引用。我有两个这些类的序列,我想基于相等的引用将它们连接起来

    Class ClassA
    {
         public string Reference {get;}
         ...
    }
    
    public ClassB
    {
         public string Reference {get;}
         ...
    }
    
    var listA = new List<ClassA>()
    {
        new ClassA() {Reference = 1, ...},
        new ClassA() {Reference = 2, ...},
        new ClassA() {Reference = 3, ...},
        new ClassA() {Reference = 4, ...},
    }
    
    var listB = new List<ClassB>()
    {
        new ClassB() {Reference = 1, ...},
        new ClassB() {Reference = 3, ...},
        new ClassB() {Reference = 5, ...},
        new ClassB() {Reference = 7, ...},
    }
    
    我不确定这是如何工作的,但我可以想象,对于listA中的每个a,listB都会被迭代,以查看listB中是否有一个b具有与a相同的引用

    问题:如果我知道引用是不同的,那么将B转换成字典并比较listA中每个元素的引用不是更有效吗

    var dictB = listB.ToDictionary<string, ClassB>()
    var myJoin = listA
        .Where(a => dictB.ContainsKey(a.Reference))
        .Select(a => new (A = a, B = dictB[a.Reference]);
    
    var dictB=listB.ToDictionary()
    var myJoin=listA
    .其中(a=>dictB.ContainsKey(a.Reference))
    .选择(a=>new(a=a,B=dictB[a.Reference]);
    
    这样,listB的每个元素必须访问一次才能放入字典,listA的每个元素必须访问一次,hascode引用必须计算一次


    这种方法对于大型收集会更快吗?

    我为此创建了一个测试程序,并测量了所用的时间

    假设我有一类Person,每个Person都有一个名字和一个Person类型的父属性。如果父属性不知道,则父属性为null

    我有一系列杂种(没有父亲),他们只有一个儿子和一个女儿。所有的女儿都放在一个序列中。所有的儿子都放在另一个序列中

    问题:加入有相同父亲的儿子和女儿

    结果:使用Enumerable.Join加入100万个家庭需要1.169秒。使用Dictionary Join加入家庭需要1.024秒。速度稍微快了一点

    守则:

    class Person : IEquatable<Person>
    {
        public string Name { get; set; }
        public Person Father { get; set; }
        // + a lot of equality functions get hash code etc
        // for those interested: see the bottom
    }
    
    const int nrOfBastards = 1000000; // one million
    var bastards = Enumerable.Range (0, nrOfBastards)
        .Select(i => new Person()
            { Name = 'B' + i.ToString(), Father = null })
        .ToList();
    
    var sons = bastards.Select(father => new Person()
            {Name = "Son of " + father.Name, Father = father})
        .ToList();
    
    var daughters = bastards.Select(father => new Person()
            {Name = "Daughter of " + father.Name, Father = father})
        .ToList();
    
    // join on same parent: Traditionally and using Dictionary
    var stopwatch = Stopwatch.StartNew();
    this.TraditionalJoin(sons, daughters);
    var time = stopwatch.Elapsed;
    Console.WriteLine("Traditional join of {0} sons and daughters took {1:F3} sec", nrOfBastards, time.TotalSeconds);
    
    stopwatch.Restart();
    this.DictionaryJoin(sons, daughters);
    time = stopwatch.Elapsed;
    Console.WriteLine("Dictionary join of {0} sons and daughters took {1:F3} sec", nrOfBastards, time.TotalSeconds);
    }
    
    private void TraditionalJoin(IEnumerable<Person> boys, IEnumerable<Person> girls)
    {   // join on same parent
        var family = boys
            .Join(girls,
                boy => boy.Father,
                girl => girl.Father,
                (boy, girl) => new { Son = boy.Name, Daughter = girl.Name })
            .ToList();
    }
    
    private void DictionaryJoin(IEnumerable<Person> sons, IEnumerable<Person> daughters)
    {
        var sonsDictionary = sons.ToDictionary(son => son.Father);
        var family = daughters
            .Where(daughter => sonsDictionary.ContainsKey(daughter.Father))
            .Select(daughter => new { Son = sonsDictionary[daughter.Father], Daughter = daughter })
            .ToList();
    }
    
    班级人员:可胜任
    {
    公共字符串名称{get;set;}
    公众人物父{get;set;}
    //+许多相等函数获得哈希码等
    //对于那些感兴趣的人:看下面
    }
    常量int nrofstards=1000000;//一百万
    var bastards=可枚举的范围(0,nrOfBastards)
    .Select(i=>newperson()
    {Name='B'+i.ToString(),Father=null})
    .ToList();
    var sons=bastards.Select(父亲=>newperson()
    {Name=“父亲的儿子”+父亲的名字,父亲=父亲})
    .ToList();
    var女儿=杂种。选择(父亲=>新人()
    {Name=“父亲的女儿”+父亲的名字,父亲=父亲})
    .ToList();
    //在同一父节点上加入:传统和使用字典
    var stopwatch=stopwatch.StartNew();
    这是传统的结合(儿子、女儿);
    var时间=秒表。已用时间;
    Console.WriteLine(“传统的{0}子和子的连接需要{1:F3}秒”,nrofstards,time.TotalSeconds);
    stopwatch.Restart();
    本词典加入(儿子、女儿);
    时间=秒表。已过;
    Console.WriteLine(“字典连接{0}个儿子和女儿花了{1:F3}秒”,nrofstards,time.TotalSeconds);
    }
    private void Traditional Join(IEnumerable男孩、IEnumerable女孩)
    {//在同一父节点上联接
    家庭=男孩
    .加入(女孩),
    男孩=>男孩,父亲,
    女孩=>女孩,父亲,
    (男孩,女孩)=>新的{儿子=男孩。名字,女儿=女孩。名字})
    .ToList();
    }
    私有void字典join(IEnumerable子,IEnumerable子)
    {
    var sonsDictionary=sons.ToDictionary(son=>son.Father);
    家庭=女儿
    .Where(女儿=>sonsDictionary.ContainsKey(女儿.父亲))
    .Select(女儿=>new{Son=sonsDictionary[女儿.Father],女儿=女儿})
    .ToList();
    }
    

    对于那些对人的平等感兴趣的人,需要一本合适的字典:

    class Person : IEquatable<Person>
    {
        public string Name { get; set; }
        public Person Father { get; set; }
    
        public bool Equals(Person other)
        {
            if (other == null)
                return false;
            else if (Object.ReferenceEquals(this, other))
                return true;
            else if (this.GetType() != other.GetType())
                return false;
            else
                return String.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
        }
    
        public override bool Equals(object obj)
        {
            return this.Equals(obj as Person);
        }
    
        public override int GetHashCode()
        {
            const int prime1 = 899811277;
            const int prime2 = 472883293;
            int hash = prime1;
            unchecked
            {
                hash = hash * prime2 + this.Name.GetHashCode();
                if (this.Father != null)
                {
                    hash = hash * prime2 + this.Father.GetHashCode();
                }
            }
            return hash;
        }
    
        public override string ToString()
        {
            return this.Name;
        }
    
        public static bool operator==(Person x, Person y)
        {
            if (Object.ReferenceEquals(x, null))
                return Object.ReferenceEquals(y, null);
            else
                return x.Equals(y);
        }
    
        public static bool operator!=(Person x, Person y)
        {
            return !(x==y);
        }
    }
    
    班级人员:可胜任
    {
    公共字符串名称{get;set;}
    公众人物父{get;set;}
    公共布尔等于(其他人)
    {
    如果(其他==null)
    返回false;
    else if(Object.ReferenceEquals(this,other))
    返回true;
    else if(this.GetType()!=other.GetType())
    返回false;
    其他的
    返回String.Equals(this.Name,other.Name,StringComparison.OrdinalIgnoreCase);
    }
    公共覆盖布尔等于(对象对象对象)
    {
    返回此.Equals(对象为个人);
    }
    公共覆盖int GetHashCode()
    {
    常量int prime1=899811277;
    常量int prime2=472883293;
    int hash=prime1;
    未经检查
    {
    hash=hash*prime2+this.Name.GetHashCode();
    if(this.Father!=null)
    {
    hash=hash*prime2+this.Father.GetH
    
    class Person : IEquatable<Person>
    {
        public string Name { get; set; }
        public Person Father { get; set; }
    
        public bool Equals(Person other)
        {
            if (other == null)
                return false;
            else if (Object.ReferenceEquals(this, other))
                return true;
            else if (this.GetType() != other.GetType())
                return false;
            else
                return String.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
        }
    
        public override bool Equals(object obj)
        {
            return this.Equals(obj as Person);
        }
    
        public override int GetHashCode()
        {
            const int prime1 = 899811277;
            const int prime2 = 472883293;
            int hash = prime1;
            unchecked
            {
                hash = hash * prime2 + this.Name.GetHashCode();
                if (this.Father != null)
                {
                    hash = hash * prime2 + this.Father.GetHashCode();
                }
            }
            return hash;
        }
    
        public override string ToString()
        {
            return this.Name;
        }
    
        public static bool operator==(Person x, Person y)
        {
            if (Object.ReferenceEquals(x, null))
                return Object.ReferenceEquals(y, null);
            else
                return x.Equals(y);
        }
    
        public static bool operator!=(Person x, Person y)
        {
            return !(x==y);
        }
    }