Farklılıklar için iki genel listeyi karşılaştırmanın en hızlı yolu


214

İki büyük (> 50.000 öğe) karşılaştırmak için en hızlı (ve en az kaynak yoğun) ve sonuç olarak aşağıdaki gibi iki liste var:

  1. ilk listede görünen ancak ikinci listede görünmeyen öğeler
  2. ikinci listede görünen ancak ilk listede olmayan öğeler

Şu anda List veya IReadOnlyCollection ile çalışıyorum ve bir linq sorgusunda bu sorunu çözmek:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

Ama bu istediğim kadar iyi bir performans sergilemiyor. Listeleri çok işlemek için bu daha hızlı ve daha az kaynak yoğun hale getirmek herhangi bir fikir?

Yanıtlar:


454

Kullanım Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

Aslında bundan biraz daha hızlı olan yaklaşımlar olduğundan şüpheleniyorum, ancak bu bile O (N * M) yaklaşımınızdan çok daha hızlı olacaktır .

Bunları birleştirmek istiyorsanız, yukarıdaki yöntemle bir yöntem ve ardından bir return ifadesi oluşturabilirsiniz:

return !firstNotSecond.Any() && !secondNotFirst.Any();

Nota Bir nokta var ki olan söz konusu orijinal kod ve burada çözelti arasındaki sonuçlarında bir farkı: birçok olarak rapor olurdu oysa sadece tek bir listede hiçbir yinelenen elemanlar sadece benim koduyla bir kez rapor edilecektir orijinal kodda göründükleri zaman.

Örneğin, listeleri içeren [1, 2, 2, 2, 3]ve [1], "Liste1 elemanların ancak list2" orijinal kodunda sonucu olacaktır [2, 2, 2, 3]. Benim kod ile sadece olurdu [2, 3]. Çoğu durumda bu bir sorun olmayacak, ancak farkında olmaya değer.


8
Bu gerçekten büyük bir performans kazancıdır! Bu cevap için teşekkürler.
Frank

2
İki büyük liste için merak ediyorum, karşılaştırmadan önce sıralamak yararlı mı? veya Uzantı yöntemi dışında, iletilen liste zaten sıralanır.
Larry

9
@Larry: Sıralanmamış; bir karma seti oluşturur.
Jon Skeet

2
@PranavSingh: Uygun eşitliğe sahip herhangi bir şey için çalışacaktır - bu nedenle özel türünüz geçersiz kılar Equals(object)ve / veya uygularsa IEquatable<T>iyi olmalıdır.
Jon Skeet

2
@ k2ibegin: Bir IEquatable<T>uygulama veya object.Equals(object)yöntem kullanacak olan varsayılan eşitlik karşılaştırıcısını kullanır . Minimal tekrarlanabilir bir örnekle yeni bir soru oluşturmanız gerektiği anlaşılıyor - yorumlardaki şeyleri gerçekten teşhis edemeyiz.
Jon Skeet

40

Daha verimli kullanmak Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

Bu yöntem ertelenmiş yürütme kullanılarak uygulanır. Bu, örneğin yazabileceğiniz anlamına gelir:

var first10 = inListButNotInList2.Take(10);

Set<T>Nesneleri karşılaştırmak için dahili olarak a kullandığından da etkilidir . İlk olarak ikinci sekanstan tüm farklı değerleri toplayıp, daha önce görülmediklerini kontrol ederek birincinin sonuçlarını aktararak çalışır.


1
Hmm. Tamamen ertelenmedi. Kısmen ertelendi diyebilirim. Tam bir Set<T>ikinci dizisinin (yani tamamen iterated ve depolanan) inşa edilmiştir, daha sonra ilk dizisinden eklenebilir öğeler döküntüler oluşur.
harcayan

2
@spender, bu, çalıştırmanın Wherekısmen ertelendiğini söylemek gibidir , çünkü list.Where(x => x.Id == 5)sayının değerinde 5tembel olarak yerine başlangıçta saklanır.
jwg

27

Numaralandırılabilir.SequenceEqual Yöntemi

Eşitlik karşılaştırıcısına göre iki dizinin eşit olup olmadığını belirler. MS.Docs

Enumerable.SequenceEqual(list1, list2);

Bu, tüm ilkel veri türleri için geçerlidir. Özel nesnelerde kullanmanız gerekiyorsa, uygulamanız gerekirIEqualityComparer

Eşitlik için nesnelerin karşılaştırılmasını destekleyen yöntemleri tanımlar.

IEqualityComparer Arayüzü

Eşitlik için nesnelerin karşılaştırılmasını destekleyen yöntemleri tanımlar. IEqualityComparer için MS.Docs


kabul edilen cevap bu olmalıdır. Soru SETS ile ilgili değil, elemanların kopyalanmasını içerebilen LISTS ile ilgili.
Adrian Nasui

3
Sonucunun SequenceEqualbasit olduğu göz önüne alındığında, bunun nasıl bir cevap olabileceğini görmüyorum bool. OP iki sonuç listesi ister - ve set işlemleri açısından ne istediklerini açıklar: "ilk listede görünen ancak ikincisinde görünmeyen öğeler". Sıralamanın ilgili olduğuna dair hiçbir belirti yokken, SequenceEqual siparişin ilgili olduğunu düşünüyor. Bu tamamen farklı bir soruyu cevaplıyor gibi görünüyor.
Jon Skeet

evet, doğru, bu çok hızlı cevap verdi ve isteğin ikinci bölümüne bakmadı gibi görünüyor ... ilk iki yorum aynı ...
miguelmpn

9

Sonuçların büyük / küçük harfe duyarsız olmasını istiyorsanız , aşağıdakiler işe yarar :

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondb1.dll içerir

secondNotFirstb2.dll içerecektir


5

Bu Sorun için değil, ama eşit ve değil için listeleri karşılaştırmak için bazı kod İşte! özdeş nesneler:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}

1
Özel veri türlerini karşılaştırabilmeniz için gereken budur. Sonra kullanınExcept
Pranav Singh

Muhtemelen sıralanabilir tiplerle daha iyisini yapabilirsiniz. O (n ^ 2) ile çalışır, O (nlogn) ile yapabilirsiniz.
yuvalm2

3

şu şekilde deneyin:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

13
Bu, ilkinde her öğe için ikinci listenin taranmasını gerektiren korkunç performanstan muzdariptir. Çalıştığı için aşağı oy değil, ancak orijinal kod kadar kötü.
harcayan

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

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

Bazen sadece iki listenin farklı olup olmadığını bilmeniz gerekir , bu farklılıkların ne olduğunu değil. Bu durumda, projenize bu uzantı yöntemini eklemeyi düşünün. Listelenen nesnelerin IEquatable uygulaması gerektiğini unutmayın!

Kullanımı:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

ComponentSınıf ne olursa olsun, burada gösterilen yöntemler Carneredeyse aynı şekilde uygulanmalıdır.

GetHashCode'u nasıl yazdığımızı not etmek çok önemlidir. Sipariş düzgün uygulamak ise IEquatable, Equalsve GetHashCode gereken bir mantıksal uyumlu şekilde örneğin özelliklerinin üzerinde işlem.

Aynı içeriklere sahip iki liste hala farklı nesnelerdir ve farklı karma kodlar oluşturur. Bu iki listenin eşit olarak ele alınmasını istediğimizden, GetHashCodeher biri için aynı değeri üretmemize izin vermeliyiz . Bunu, hashcode'u listedeki her öğeye devrederek ve hepsini birleştirmek için standart bitsel XOR'u kullanarak başarabiliriz. XOR sıraya göre değişmez, bu nedenle listelerin farklı sıralanması önemli değildir. Sadece eşdeğer üyelerden başka bir şey içermemeleri önemlidir.

Not: garip ad, yöntemin listedeki öğelerin sırasını dikkate almadığı anlamına gelir. Listedeki öğelerin sırasını önemsiyorsanız, bu yöntem sizin için değildir!


1

Milyonlarca kayıt bulunan iki listeyi karşılaştırmak için bu kodu kullandım.

Bu yöntem çok zaman almayacak

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }

0

Yalnızca birleşik sonuç gerekiyorsa, bu da işe yarayacaktır:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

burada T, liste elemanı türüdür.


-1

Komik olabilir, ama benim için çalışıyor

string.Join ("", List1)! = string.Join ("", Liste2)


burada yazıldığı gibi <string> veya List <int> Listeleri için bile işe yaramaz, örneğin iki liste 11; 2; 3 ve 1; 12; 3, dizelerle bazılarına katılmadığınız için aynı olacaktır listede olası bir öğe olmayan benzersiz ayırıcı. Bunun dışında, çok sayıda öğe içeren bir liste için dizeleri birleştirmek muhtemelen bir performans katilidir.
SwissCoder

@SwissCoder: Yanılıyorsunuz, bu dize için bir performacne katili değil. 50.000 dizeyle (her biri 3 uzunlukta) iki listeniz varsa, bu algoritmanın makinemde 3 ms olması gerekir. Kabul edilen cevap 7 gerekiyor. Bence hile Jibz sadece bir dize karşılaştırma ihtiyacı var. Tabii ki benzersiz bir ayırıcı eklemesi gerekiyor.
user1027167

@ user1027167: Dizeleri doğrudan karşılaştırmaktan bahsetmiyorum (bu da soru değil). 50.000 nesnesi olan bir Listedeki tüm nesnelerin .ToString () yöntemini çağırmak, uygulanma şekline bağlı olarak büyük bir dize oluşturabilir. Bence yol bu değil. Daha sonra "benzersiz" bir karakter veya dizeye güvenmek de risklidir, kod gerçekten böyle kullanılamaz.
SwissCoder

Tamam bu doğru. Soru soran, listelerinin veri tipini vermeden en hızlı yolu istedi. Muhtemelen bu cevap, soru soran kişinin kullanım durumu için en hızlı yoldur.
user1027167


-4

Bulabileceğiniz en iyi çözüm

var list3 = list1.Where(l => list2.ToList().Contains(l));

1
Bu aslında çok kötü çünkü List<T>içindeki her eleman için yeni bir şey yaratıyor list1. Ayrıca sonuç olarak adlandırılır list3o olmadığı zaman List<T>.
Wai Ha Lee
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.