Cara sederhana untuk mengetahui apakah dua daftar berbeda mengandung elemen yang sama persis?

253

Apa cara paling sederhana untuk menemukan jika dua Daftar berisi elemen yang persis sama, di perpustakaan Java standar?

Seharusnya tidak masalah jika kedua Daftar adalah instance yang sama atau tidak, dan seharusnya tidak masalah jika parameter tipe Daftar berbeda.

misalnya

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

Mungkin ada sesuatu yang menatapku di wajah, aku tahu :-)


EDIT: Untuk memperjelas, saya mencari EXACT elemen yang sama dan jumlah elemen, secara berurutan.

Grundlefleck
sumber
Apakah elemen-elemen harus berada dalam urutan yang sama?
Michael Myers
Ini mungkin tidak pernah memengaruhi Anda, tetapi berhati-hatilah karena hibernate set persistent terkadang tidak menghormati kontrak yang sama - cari lihat opensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim

Jawaban:

367

Jika Anda peduli tentang pesanan, maka gunakan metode sama dengan:

list1.equals(list2)

Dari javadoc:

Membandingkan objek yang ditentukan dengan daftar ini untuk persamaan. Mengembalikan nilai true jika dan hanya jika objek yang ditentukan juga daftar, kedua daftar memiliki ukuran yang sama, dan semua pasangan elemen yang sesuai dalam dua daftar adalah sama. (Dua elemen e1 dan e2 sama jika (e1 == null? E2 == null: e1.equals (e2)).) Dengan kata lain, dua daftar didefinisikan sama jika mengandung elemen yang sama dalam urutan yang sama . Definisi ini memastikan bahwa metode yang sama berfungsi dengan baik di seluruh implementasi berbeda dari antarmuka Daftar.

Jika Anda ingin memeriksa independen dari pesanan, Anda bisa menyalin semua elemen ke Set dan menggunakan sama dengan Set yang dihasilkan:

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

Keterbatasan pendekatan ini adalah tidak hanya mengabaikan ketertiban, tetapi juga frekuensi elemen duplikat. Misalnya, jika list1["A", "B", "A"] dan list2[[A], "B", "B"] Setpendekatan akan menganggap mereka sama.

Jika Anda harus tidak sensitif untuk memesan tetapi peka terhadap frekuensi duplikat Anda dapat:

Laurence Gonsalves
sumber
54
Tidak bisakah Anda menggunakan ContallAll jika Anda ingin memeriksa independen pesanan?
laz
6
Saya tidak tahu tentang detail implementasi dariAllAll, tetapi sepertinya itu bisa buruk. Jika berisi semua panggilan berisi () berulang-ulang, Anda akan memiliki O (n ^ 2) alg. Set keseluruhan harus O (nlogn)
Tom
6
Sebenarnya, jika set hanya akan menjadi O (nlogn), pendekatan lain adalah memanggil Collections.sort () pada daftar, dan kemudian gunakan equals. Jika Anda ingin mempertahankan pesanan, Anda harus menyalin daftar, dan itu mungkin mahal dan mendukung solusi yang ditetapkan ... jadi Anda harus memikirkan situasi Anda :-).
Tom
1
@amischiefr: apakah Anda menyarankan O (n ^ 2) adalah yang terbaik yang dapat Anda lakukan?
Tom
8
@ Dennis Pemeriksaan ukuran hanya berfungsi jika Anda tahu bahwa setiap daftar hanya berisi elemen yang berbeda. Misalnya, diberikan a = [x, y, x]dan b = [x, y, z]kemudian ukurannya sama dan b.containsAll(a)akan mengembalikan true, tetapi bmengandung elemen tidak masuk a.
Laurence Gonsalves
95

Saya memposting banyak hal dalam komentar saya pikir itu menjamin jawabannya sendiri.

Seperti yang dikatakan semua orang di sini, menggunakan equals () tergantung pada urutannya. Jika Anda tidak peduli tentang pesanan, Anda memiliki 3 opsi.

Pilihan 1

Gunakan containsAll(). Opsi ini tidak ideal, menurut saya, karena ia menawarkan kinerja kasus terburuk, O (n ^ 2).

pilihan 2

Ada dua variasi untuk ini:

2a) Jika Anda tidak peduli tentang menjaga urutan daftar Anda ... gunakan Collections.sort()di kedua daftar. Kemudian gunakan equals(). Ini adalah O (nlogn), karena Anda melakukan dua macam, dan kemudian perbandingan O (n).

2b) Jika Anda perlu mempertahankan urutan daftar, Anda dapat menyalin kedua daftar terlebih dahulu. MAKA Anda dapat menggunakan solusi 2a pada kedua daftar yang disalin. Namun ini mungkin tidak menarik jika menyalin sangat mahal.

Ini mengarah ke:

Opsi 3

Jika persyaratan Anda sama dengan bagian 2b , tetapi menyalin terlalu mahal. Anda dapat menggunakan TreeSet untuk melakukan penyortiran untuk Anda. Buang setiap daftar ke TreeSet sendiri. Ini akan disortir dalam set, dan daftar asli akan tetap utuh. Kemudian lakukan equals()perbandingan pada keduanya TreeSet. The TreeSetss dapat dibangun dalam O (nlogn) waktu, dan equals()adalah O (n).

Ambil pilihanmu :-).

EDIT: Saya hampir lupa peringatan yang sama yangditunjukkan Laurence Gonsalves . Implementasi TreeSet akan menghilangkan duplikat. Jika Anda peduli tentang duplikat, Anda akan membutuhkan semacam multiset yang diurutkan.

Tom
sumber
Jika Anda peduli tentang duplikat Anda selalu dapat menguji bahwa ukuran koleksi sama sebelum tes lainnya.
laz
Lebih khusus lagi, jika memiliki duplikat menunjukkan ketidaksetaraan, ukuran daftar harus sama sebelum pemeriksaan kesetaraan memiliki peluang untuk berhasil.
laz
7
@ laz: memeriksa ukuran tidak akan berfungsi jika elemen yang berbeda diduplikasi dalam dua daftar. misalnya: [A, A, B] vs [A, B, B] berukuran sama.
Laurence Gonsalves
@Laurence: Saya setuju bahwa postingan malas agak membingungkan (saya membacanya beberapa kali sebelum saya memahaminya). Saya menganggap bahwa dia hanya berusaha memberikan "jalan pintas" untuk kasus khusus ketika 2 syarat berlaku: (1) duplikat penting, dan (2) ukuran daftar berbeda. Dalam contoh Anda, saya pikir laz masih mengatakan perlu untuk melakukan semua pemeriksaan yang sama yang kita bahas. (Setidaknya begitulah cara saya membacanya). Jika duplikat JANGAN penting, maka Anda tidak dapat menggunakan ukuran sebagai pemeriksaan kasus khusus. Tetapi ketika 2 kondisi bertahan, Anda bisa mengatakan "jika (list1.size ()! = List2.size ()) mengembalikan false ;.
Tom
9
Berisi Semua yang saya pikir akan memberikan jawaban yang salah, Anda akan perlu memuat semua cara. a.containsAll(b) && b.containsAll(a)
Richard Tingle
24

Jika Anda menggunakan (atau senang menggunakan) Koleksi Apache Commons, Anda dapat menggunakan CollectionUtils.isEqualCollection yang "mengembalikan true jika Koleksi yang diberikan mengandung elemen yang persis sama dengan kardinalitas yang persis sama."

daiscog
sumber
Implementasi berbasis hashmap yang sangat bagus. Runtime harus O (n), dan jika ada banyak elemen berulang, ia menggunakan memori minimal untuk melacak (pada dasarnya melacak frekuensi (kardinalitas) elemen menggunakan peta untuk setiap koleksi). Kelemahannya adalah ia memiliki tambahan penggunaan memori O (n).
Muhd
17

Sangat terlambat ke pesta tetapi ingin menambahkan cek aman nol ini:

Objects.equals(list1, list2)
Reimeus
sumber
8

Saya tahu ini adalah utas lama, tetapi tidak ada jawaban lain yang sepenuhnya memecahkan kasus penggunaan saya (saya kira Guava Multiset mungkin melakukan hal yang sama, tetapi tidak ada contoh di sini). Maafkan pemformatan saya. Saya masih baru untuk memposting di stack stack. Selain itu beri tahu saya jika ada kesalahan

Katakanlah Anda memiliki List<T>a dan List<T>b dan Anda ingin memeriksa apakah mereka sama dengan kondisi berikut:

1) O (n) waktu berjalan yang diharapkan
2) Kesetaraan didefinisikan sebagai: Untuk semua elemen dalam a atau b, berapa kali elemen terjadi dalam a sama dengan berapa kali elemen tersebut terjadi dalam b. Elemen kesetaraan didefinisikan sebagai T.equals ()

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
    if(a==null) {
        if(b==null) {
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
        } else {
            return false;
        }
    }
    if(b==null) {
        return false;
    }
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            tempMap.put(element, 1);
        } else {
            tempMap.put(element, currentCount+1);
        }
    }
    for(Object element : b) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            return false;
        } else {
            tempMap.put(element, currentCount-1);
        }
    }
    for(Integer count : tempMap.values()) {
        if(count != 0) {
            return false;
        }
    }
    return true;
}

Waktu berjalan adalah O (n) karena kita melakukan penyisipan O (2 * n) ke dalam hashmap dan O (3 * n) memilih hashmap. Saya belum sepenuhnya menguji kode ini, jadi waspadalah :)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);
Andrew
sumber
5

Coba versi ini yang tidak membutuhkan urutan yang sama tetapi mendukung memiliki kelipatan dari nilai yang sama. Mereka cocok hanya jika masing-masing memiliki jumlah nilai yang sama.

public boolean arraysMatch(List<String> elements1, List<String> elements2) {
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) {
        return false;
    }
    List<String> work = newArrayList(elements2);
    for (String element : elements1) {
        if (!work.remove(element)) {
            return false;
        }
    }
    return work.isEmpty();
}
Lee Meador
sumber
work.remove (elemen) adalah O (n), jadi solusi ini adalah O (n ^ 2)
Andrew
Atau O (n1 * n2) yang agak sama
Lee Meador
Saya juga menggunakan strategi yang sama karena menangani semua skenario dan ukuran koleksi tidak sebesar itu maka O (n ^ 2) tidak masalah
Naresh Joshi
3

Metode equals pada Daftar akan melakukan ini, Daftar diperintahkan, sehingga untuk menjadi sama dua Daftar harus memiliki elemen yang sama dalam urutan yang sama.

return list1.equals(list2);
daveb
sumber
3
Daftar tidak dipesan kecuali Anda mengurutkannya.
Michael Myers
Sigh @ Myself. Jawaban yang sangat jelas. Anda tahu ini sudah terlalu lama ketika Anda bahkan tidak bisa lagi Ctrl + F halaman web. :)
Grundlefleck
2
@mmyers: item dalam daftar tidak dipesan kecuali Anda mengurutkannya. Daftar itu sendiri memiliki urutan item tersirat (berdasarkan indeks), yang tidak berubah kecuali jika Anda mengubah item dalam daftar. (vs. Set atau Koleksi di mana tidak ada jaminan pemesanan yang konsisten jika Anda mengulanginya dua kali)
Jason S
Saya pikir apa yang dimaksud daveb dengan mengatakan daftar diurutkan adalah List.equals mempertimbangkan urutan elemen-elemen itu untuk menentukan kesetaraan. Lihat Javadoc.
laz
2
Maksud saya adalah daftar yang berisi {"A", "B"} dan daftar yang berisi {"B", "A"} tidak akan sama dengan metode ini. Mungkin memang itu yang dimaksudkan, tetapi saya ingin memastikan tidak ada yang mengabaikannya.
Michael Myers
3

Solusi untuk kasus ketika dua daftar memiliki elemen yang sama, tetapi urutannya berbeda:

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
    if(isNullLists(listOne, listTwo)) {
        return false;
    }

    if (hasDifferentSize(listOne, listTwo)) {
        return true;
    }

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);
}

private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
    return listOne == null && listTwo == null;
}

private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
}
Pavlo Zvarych
sumber
2
Saya pikir Anda tidak perlu menyalin listTwo.
AjahnCharles
1
Anda mungkin juga ingin mencatat mengapa Anda menggunakan removeAll()alih-alih containsAll()(pemahaman saya adalah bahwa jika listTwo berisi duplikat yang terkandung hanya sekali dalam listOne, pendekatan containAll () akan melaporkan daftar dengan benar sebagai yang sama).
AjahnCharles
3

Jawaban Tom sangat bagus. Saya setuju sepenuhnya dengan jawabannya!

Aspek yang menarik dari pertanyaan ini adalah, apakah Anda memerlukan Listjenis itu sendiri dan urutan bawaannya.

Jika tidak, Anda dapat menurunkan Iterableatau Collectionyang memberi Anda beberapa fleksibilitas dalam melewati struktur data yang diurutkan pada waktu penyisipan, daripada pada saat Anda ingin memeriksa.

Jika pesanan tidak pernah penting (dan Anda tidak memiliki duplikat elemen) mempertimbangkan menggunakan Set.

Jika pesanan penting tetapi ditentukan oleh waktu penyisipan (dan Anda tidak memiliki duplikat) pertimbangkan LinkedHashSetyang seperti TreeSet tetapi dipesan berdasarkan waktu penyisipan (duplikat tidak dihitung). Ini juga memberi Anda O(1)akses yang diamortisasi O(log n).

Alex
sumber
2

Kode sampel:

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) {

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) {
        sizePrevoisList = previousList.size();
    }
    if (newList != null && !newList.isEmpty()) {
        sizeNewList = newList.size();
    }

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
        return false;
    }

    if (sizeNewList != sizePrevoisList) {
        return true;
    }

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try {
        Collections.sort(n_prevois);
        Collections.sort(n_new);
    } catch (ClassCastException exp) {
        return true;
    }

    for (int i = 0; i < sizeNewList; i++) {
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) {
            // Object are same
        } else {
            return true;
        }
    }

    return false;
}
Jaydip Halake
sumber
2

Selain jawaban Laurence, jika Anda juga ingin menjadikannya nol-aman:

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}
Felixuko
sumber
1
Anda dapat menyederhanakan pemeriksaan:if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus
Tidak berfungsi jika daftar tersebut [a, a, b, c] & [a, b, c] dan akan kembali benar kecuali menambahkan pemeriksaan tambahan untuk memastikan ukuran daftar sama.
Venkat Madhav
2
list1.equals(list2);

Jika daftar Anda mengandung MyClass Kelas kustom, kelas ini harus menimpa equalsfungsi.

 class MyClass
  {
  int field=0;
  @0verride
  public boolean equals(Object other)
        {
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        }
  }

Catatan: jika Anda ingin menguji sama dengan pada java.util.Set daripada a java.util.List, maka objek Anda harus menimpa hashCode fungsi.

Pierre
sumber
1
seharusnya baris: return this.field == MyClass.class.cast (lainnya); kembalikan this.field == MyClass.class.cast (lainnya) .field;
alpere
@alpere oh! kamu benar ! Saya akan memperbaikinya. Terima kasih!
Pierre
0

Anda dapat menggunakan perpustakaan Apache.apache.commons.collections: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)
David Zhao
sumber
Ini juga mengharuskan elemen daftar berada dalam urutan yang sama.
josh-cain
Anda dapat mengurutkan daftar sebelum membandingkan
David Zhao
Tentu, Anda bisa melakukan itu asalkan tipe yang disimpan dalam daftar atau sortir (atau Anda memiliki pengaturan pembanding). Namun, algoritma implementasi Apache tidak berbeda dari list1.equals biasa (list2), kecuali statis. Saya melihat di mana saya salah memahami pertanyaan itu dan sebenarnya menanyakan bagaimana membandingkan item daftar dalam urutan yang sama. Salahku!
josh-cain
@ DavidZhao: tautannya sudah mati.
Aniket Kulkarni
0

Periksa kedua daftar bukan nol. Jika ukurannya berbeda, maka daftar ini tidak sama. Buat peta yang terdiri dari elemen daftar sebagai kunci dan pengulangannya sebagai nilai dan bandingkan peta.

Asumsi, jika kedua daftar adalah nol, saya menganggapnya sama.

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

Harap dicatat, metode yang sama harus didefinisikan dengan benar untuk objek-objek ini. https://stackoverflow.com/a/24814634/4587961

Yan Khonski
sumber
1
Anda mengasumsikan sebuah elemen tidak dapat menampilkan berapa kali berbeda dalam setiap daftar mis. [x, x, y]Vs [x, y, y]akan kembali benar dengan implementasi Anda.
AjahnCharles
@CodeConfident, terima kasih banyak! Saya memperbarui jawabannya. Saya akan menggunakan mao!
Yan Khonski
-2

Itu tergantung pada kelas Daftar konkret apa yang Anda gunakan. Kelas abstrak AbstractCollection memiliki metode yang disebut berisiAll (Koleksi) yang mengambil koleksi lain (Daftar adalah koleksi) dan:

Mengembalikan nilai true jika koleksi ini mengandung semua elemen dalam koleksi yang ditentukan.

Jadi jika ArrayList dilewatkan, Anda dapat memanggil metode ini untuk melihat apakah mereka persis sama.

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

Alasan untuk containAll () adalah karena iterasi melalui daftar pertama mencari kecocokan di daftar kedua. Jadi jika mereka salah urutan sama dengan () tidak akan mengambilnya.

EDIT: Saya hanya ingin memberikan komentar di sini tentang waktu berjalan diamortisasi melakukan berbagai opsi yang ditawarkan. Apakah waktu berjalan itu penting? Tentu. Apakah itu satu-satunya hal yang harus Anda pertimbangkan? Tidak.

Biaya menyalin SETIAP elemen tunggal dari daftar Anda ke daftar lain membutuhkan waktu, dan itu juga memakan banyak memori (secara efektif menggandakan memori yang Anda gunakan).

Jadi jika memori di JVM Anda tidak menjadi masalah (yang seharusnya pada umumnya) maka Anda masih perlu mempertimbangkan waktu yang diperlukan untuk menyalin setiap elemen dari dua daftar menjadi dua TreeSets. Ingat itu menyortir setiap elemen saat memasuki mereka.

Saran terakhir saya? Anda perlu mempertimbangkan kumpulan data Anda dan berapa banyak elemen yang Anda miliki dalam kumpulan data Anda, dan juga seberapa besar setiap objek dalam kumpulan data Anda sebelum Anda dapat membuat keputusan yang baik di sini. Main-main dengan mereka, buat satu jalan sekali dan lihat mana yang berjalan lebih cepat. Ini latihan yang bagus.

amischiefr
sumber
2
Bukankah harus foo.containsAll (bar) && bar.containsAll (foo); ?
Carl Manaster
Tidak, ia menelusuri setiap elemen di foo dan melihat apakah bilah berisi elemen itu. Ini kemudian memastikan bahwa panjangnya sama dari dua daftar. Jika untuk setiap foo ada elemen di bar sehingga foo.element == bar.element dan foo.length == bar.length maka mereka mengandung elemen yang sama.
amischiefr
apakah kita tahu jika ada jaminan efisiensi? atau apakah ini biasanya O (n ^ 2)?
Tom
Seperti array lain yang beralih melalui mencari elemen yang cocok, waktu terburuknya adalah O (n ^ 2). Dalam hal ini, sepertinya implementasinya memang iterasi melalui satu elemen pada satu waktu mencari pertandingan. Saya tidak akan berspekulasi pada waktu berjalan diamortisasi, tapi ya kasus terburuk adalah O (n ^ 2).
amischiefr
1
Ini tidak berfungsi: {1,2,2} .containsAll ({1,1,2}) dan sebaliknya, dan kedua daftar memiliki ukuran yang sama.
comco