Cara tercepat untuk membandingkan dua daftar umum untuk perbedaan

214

Apa yang tercepat (dan paling sedikit sumber daya intensif) untuk membandingkan dua besar (> 50.000 item) dan sebagai hasilnya memiliki dua daftar seperti yang di bawah ini:

  1. item yang muncul di daftar pertama tetapi tidak di yang kedua
  2. item yang muncul di daftar kedua tetapi tidak di yang pertama

Saat ini saya sedang bekerja dengan Daftar atau IReadOnlyCollection dan menyelesaikan masalah ini dalam permintaan LINQ:

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

Tapi ini tidak berkinerja sebaik yang saya inginkan. Adakah ide untuk membuat ini lebih cepat dan lebih sedikit sumber daya intensif karena saya perlu memproses banyak daftar?

jujur
sumber

Jawaban:

452

Gunakan Except:

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

Saya menduga ada pendekatan yang sebenarnya akan sedikit lebih cepat dari ini, tetapi bahkan ini akan jauh lebih cepat daripada pendekatan O (N * M) Anda.

Jika Anda ingin menggabungkan ini, Anda bisa membuat metode dengan yang di atas dan kemudian kembali pernyataan:

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

Satu titik untuk dicatat adalah bahwa ada adalah perbedaan dalam hasil antara kode asli dalam pertanyaan dan solusi di sini: setiap elemen duplikat yang hanya dalam satu daftar hanya akan dilaporkan sekali dengan kode saya, sedangkan mereka akan dilaporkan sebanyak kali seperti yang terjadi pada kode asli.

Misalnya, dengan daftar [1, 2, 2, 2, 3]dan [1], "elemen dalam list1 tetapi tidak list2" akan menghasilkan kode asli [2, 2, 2, 3]. Dengan kode saya itu hanya akan menjadi [2, 3]. Dalam banyak kasus hal itu tidak akan menjadi masalah, tetapi perlu diperhatikan.

Jon Skeet
sumber
8
Ini benar-benar keuntungan kinerja yang sangat besar! Terima kasih atas jawaban ini.
Frank
2
Saya ingin tahu dua daftar besar, apakah berguna untuk menyortir sebelum membandingkan? atau di dalam Kecuali metode ekstensi, daftar yang diteruskan sudah diurutkan.
Larry
9
@Larry: Tidak disortir; itu membangun hash set.
Jon Skeet
2
@PranavSingh: Ini akan bekerja untuk apa pun yang memiliki kesetaraan yang sesuai - jadi jika jenis kustom Anda menimpa Equals(object)dan / atau mengimplementasikannya IEquatable<T>harus baik-baik saja.
Jon Skeet
2
@ k2ibegin: Ini menggunakan pembanding kesetaraan default, yang akan menggunakan IEquatable<T>implementasi atau object.Equals(object)metode. Sepertinya Anda harus membuat pertanyaan baru dengan contoh minimal yang dapat direproduksi - kami tidak dapat benar-benar mendiagnosis sesuatu dalam komentar.
Jon Skeet
40

Lebih efisien akan menggunakan Enumerable.Except:

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

Metode ini diimplementasikan dengan menggunakan eksekusi yang ditangguhkan. Itu artinya Anda bisa menulis misalnya:

var first10 = inListButNotInList2.Take(10);

Ini juga efisien karena secara internal menggunakan Set<T>untuk membandingkan objek. Ini bekerja dengan terlebih dahulu mengumpulkan semua nilai yang berbeda dari urutan kedua, dan kemudian streaming hasil yang pertama, memeriksa bahwa mereka belum pernah terlihat sebelumnya.

Tim Schmelter
sumber
1
Hmm. Tidak cukup ditangguhkan. Saya akan mengatakan sebagian ditangguhkan. Lengkap Set<T>dibangun dari urutan kedua (yaitu sepenuhnya iterated dan disimpan), maka item yang dapat ditambahkan dari urutan pertama dihasilkan.
pemboros
2
@pender, itu seperti mengatakan bahwa eksekusi Whereditangguhkan sebagian karena list.Where(x => x.Id == 5)nilai nomor 5disimpan di awal, bukan dieksekusi dengan malas.
jwg
27

Enumerable.EequenceEqual Method

Menentukan apakah dua urutan sama menurut pembanding kesetaraan. MS.Doc

Enumerable.SequenceEqual(list1, list2);

Ini berfungsi untuk semua tipe data primitif. Jika Anda perlu menggunakannya pada objek khusus yang perlu Anda terapkanIEqualityComparer

Menentukan metode untuk mendukung perbandingan objek untuk kesetaraan.

Antarmuka IEqualityComparer

Menentukan metode untuk mendukung perbandingan objek untuk kesetaraan. MS.Document untuk IEqualityComparer

miguelmpn
sumber
ini harus menjadi jawaban yang diterima. Pertanyaannya bukan tentang SETS tetapi tentang LISTS, yang dapat berisi duplikasi elemen.
Adrian Nasui
3
Saya tidak melihat bagaimana ini bisa menjadi jawaban, mengingat bahwa hasilnya SequenceEqualsederhana bool. OP menginginkan dua daftar hasil - dan menjelaskan apa yang mereka inginkan dalam hal operasi yang ditetapkan: "item yang muncul dalam daftar pertama tetapi tidak dalam yang kedua". Tidak ada indikasi bahwa pemesanan relevan, sedangkan SequenceEqual tidak mempertimbangkan untuk menjadi relevan. Tampaknya ini menjawab pertanyaan yang sama sekali berbeda.
Jon Skeet
ya, benar, sepertinya saya menjawab yang ini terlalu cepat dan tidak melihat bagian kedua dari permintaan ... sama seperti dua komentar pertama ...
miguelmpn
9

Jika Anda ingin hasil menjadi case-sensitive , yang berikut ini akan berfungsi:

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();

firstNotSecondakan mengandung b1.dll

secondNotFirstakan mengandung b2.dll

e.gad
sumber
5

Bukan untuk Masalah ini, tapi di sini ada beberapa kode untuk membandingkan daftar yang sama dan tidak! benda identik:

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);
}
Pius Hermit
sumber
1
Inilah yang Anda butuhkan untuk dapat membandingkan tipe data khusus. Kemudian gunakanExcept
Pranav Singh
Anda mungkin bisa melakukan yang lebih baik dengan tipe yang bisa diurutkan. Ini berjalan di O (n ^ 2), sementara Anda bisa melakukan O (nlogn).
yuvalm2
3

coba cara ini:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
Ali Issa
sumber
13
Ini menderita dari kinerja yang mengerikan, membutuhkan pemindaian daftar kedua untuk setiap item di yang pertama. Tidak downvoting karena berfungsi, tetapi seburuk kode aslinya.
pemboros
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;
        }
    }
}

Terkadang Anda hanya perlu tahu apakah dua daftar itu berbeda, dan bukan apa perbedaannya. Dalam hal itu, pertimbangkan untuk menambahkan metode ekstensi ini ke proyek Anda. Perhatikan bahwa objek Anda yang terdaftar harus mengimplementasikan IEquatable!

Pemakaian:

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
}

Apa pun Componentkelasnya, metode yang ditunjukkan di sini Carharus diimplementasikan hampir secara identik.

Sangat penting untuk mencatat bagaimana kami telah menulis GetHashCode. Untuk menerapkan dengan benar IEquatable,Equals dan GetHashCode harus beroperasi pada properti instance dengan cara yang kompatibel secara logis.

Dua daftar dengan konten yang sama masih objek yang berbeda, dan akan menghasilkan kode hash yang berbeda. Karena kita ingin kedua daftar ini diperlakukan sama, kita harus membiarkan GetHashCodemenghasilkan nilai yang sama untuk masing-masingnya. Kita dapat melakukannya dengan mendelegasikan kode hash ke setiap elemen dalam daftar, dan menggunakan bitor XOR standar untuk menggabungkan semuanya. XOR adalah agnostik-urutan, jadi tidak masalah jika daftar diurutkan secara berbeda. Yang penting mereka hanya berisi anggota yang setara.

Catatan: nama yang aneh adalah untuk menyiratkan fakta bahwa metode ini tidak mempertimbangkan urutan elemen dalam daftar. Jika Anda peduli dengan urutan elemen dalam daftar, metode ini bukan untuk Anda!

Devon Parsons
sumber
1

Saya telah menggunakan kode ini untuk membandingkan dua daftar yang memiliki jutaan catatan.

Metode ini tidak akan memakan banyak waktu

    //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;
    }
Sathish
sumber
0

Jika hanya diperlukan hasil gabungan, ini juga akan berfungsi:

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

di mana T adalah jenis elemen daftar.

Ali Khaleghi Karsalari
sumber
-1

Mungkin ini lucu, tetapi bekerja untuk saya

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

Jibz
sumber
seperti yang tertulis di sini bahkan tidak akan berfungsi untuk List <string> atau List <int>, seperti misalnya dua daftar 11; 2; 3 dan 1; 12; 3 akan sama karena Anda tidak bergabung dengan string dengan beberapa pemisah unik yang bukan item yang mungkin ada dalam daftar. Selain itu, rangkaian string untuk daftar dengan banyak item mungkin adalah pembunuh kinerja.
SwissCoder
@SwissCoder: Anda salah, ini bukan pembunuh berkinerja untuk string. Jika Anda memiliki dua daftar dengan 50.000 string (masing-masing dengan panjang 3), algoritma ini membutuhkan 3 ms pada mesin saya. Jawaban yang diterima perlu 7. Saya pikir triknya adalah Jibz hanya perlu satu perbandingan string. Tentu saja dia harus menambahkan pemisah yang unik.
user1027167
@ user1027167: Saya tidak berbicara tentang membandingkan string secara langsung (karena ini juga bukan pertanyaannya). Memanggil metode .ToString () dari semua objek dalam Daftar dengan 50.000 objek dapat membuat string besar, tergantung bagaimana penerapannya. Saya tidak berpikir itu cara untuk pergi. Maka itu juga berisiko untuk bergantung pada karakter atau string yang "unik", kode tidak akan benar-benar dapat digunakan kembali seperti itu.
SwissCoder
Ok itu benar. Si penanya meminta cara tercepat tanpa memberikan tipe data dari daftarnya. Mungkin jawaban ini adalah cara tercepat untuk kasus penggunaan si penanya.
user1027167
-3

Saya pikir ini adalah cara sederhana dan mudah untuk membandingkan dua elemen daftar dengan elemen

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)
pengguna10915707
sumber
3
Ini adalah pertanyaan C #, dan Anda belum memberikan kode C #.
Wai Ha Lee
1
Mungkin Anda dapat menghapus jawaban ini dan memindahkannya ke (misalnya) Bagaimana saya bisa membandingkan dua daftar dalam python dan mengembalikan kecocokan ?
Wai Ha Lee
-4

Ini adalah solusi terbaik yang akan Anda temukan

var list3 = list1.Where(l => list2.ToList().Contains(l));
Fajoui El Mahdi
sumber
1
Ini sebenarnya sangat buruk karena ia membuat yang baru List<T>untuk setiap elemen dalam list1. Juga hasilnya dipanggil list3ketika bukan List<T>.
Wai Ha Lee