İki farklı listenin tam olarak aynı öğeleri içerip içermediğini bulmanın kolay yolu?


253

İki Liste'nin standart Java kitaplıklarında tam olarak aynı öğeleri içerip içermediğini bulmanın en kolay yolu nedir?

İki Listenin aynı örnek olup olmadığı veya Listelerin type parametresinin farklı olup olmadığı önemli olmamalıdır.

Örneğin

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

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

Muhtemelen yüzüme bakan bir şey var biliyorum biliyorum :-)


DÜZENLEME: Açıklığa kavuşturmak için, sırayla EXACT aynı öğeleri ve öğe sayısını arıyordum.


Elemanlar aynı sırada mı olmalı?
Michael Myers

Bu sözleşme eşit olduğu kalıcı setleri bazen onur yok hazırda bekleme sizi etkileyecek ama dikkat asla olabilir - arama bkz opensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim

Yanıtlar:


367

Siparişi önemsiyorsanız, eşittir yöntemini kullanın:

list1.equals(list2)

Javadoc'tan:

Eşitlik için belirtilen nesneyi bu listeyle karşılaştırır. Yalnızca belirtilen nesne de bir listeyse, her iki liste de aynı boyuta sahipse ve iki listedeki karşılık gelen tüm öğe çiftleri eşitse true değerini döndürür. (E1 ve e2 öğelerinin iki öğesi eşitse (e1 == null? E2 == null: e1.equals (e2)). Başka bir deyişle, aynı öğeleri aynı sırayla içeriyorlarsa iki liste eşit olarak tanımlanır . Bu tanım, eşitleme yönteminin Liste arabiriminin farklı uygulamalarında düzgün çalışmasını sağlar.

Siparişten bağımsız olarak kontrol etmek isterseniz, tüm öğeleri Setlere kopyalayabilir ve elde edilen Setlerde eşittir kullanabilirsiniz:

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

Bu yaklaşımın bir sınırlaması, sadece düzeni değil, aynı zamanda yinelenen öğelerin sıklığını da göz ardı etmesidir. Örneğin list1, ["A", "B", "A"] ve list2["A", "B", "B"] Setolsaydı , yaklaşım bunların eşit olduğunu düşünürdü.

Siparişe duyarsız olmanız, ancak kopyaların sıklığına duyarlı olmanız gerekirse:


54
Siparişten bağımsız olarak kontrol etmek istiyorsanız, includeAll'i kullanamaz mısınız?
laz

6
İncludeAll'ın uygulama ayrıntılarını bilmiyorum, ancak kötü gibi görünüyor. İncludeAll çağrıları defalarca () içeriyorsa, O (n ^ 2) algınız olacaktır. Kümeler O (nlogn) olmalı
Tom

6
Aslında, kümeler O (nlogn) olacaksa, başka bir yaklaşım bir listede Collections.sort () öğesini çağırmak ve eşittir. Siparişi korumak istiyorsanız, listeyi kopyalamanız gerekir ve bu pahalı olabilir ve ayarlanan çözümü tercih edebilir ... böylece durumunuzu düşünmeniz gerekir :-).
Tom

1
@ amischiefr: O'nun (n ^ 2) yapabileceğiniz en iyisi olduğunu mu düşünüyorsunuz?
Tom

8
@Dennis Boyut kontrolü gerçekten sadece her listenin sadece farklı öğeler içerdiğini biliyorsanız çalışır. Örneğin, verilen a = [x, y, x]ve b = [x, y, z]sonra boyutları eşittir ve b.containsAll(a)true değerini döndürür, ancak biçinde olmayan bir öğe içerir a.
Laurence Gonsalves

95

Kendi cevabını gerektirdiğini düşünüyorum yorumlarda bir sürü şey yayınladım.

Herkesin dediği gibi, equals () kullanımı sıraya bağlıdır. Siparişle ilgilenmiyorsanız 3 seçeneğiniz var.

seçenek 1

Kullanın containsAll(). Bence bu seçenek ideal değil çünkü en kötü durum performansı sunuyor O (n ^ 2).

seçenek 2

Bunun iki varyasyonu vardır:

2a) Listelerinizin sırasını korumakla ilgilenmiyorsanız ... Collections.sort()her iki listede de kullanın . Ardından equals(). Bu O (nlogn), çünkü iki tür ve sonra bir O (n) karşılaştırması yaparsınız.

2b) Listelerin sırasını korumanız gerekiyorsa, önce her iki listeyi de kopyalayabilirsiniz. Ardından , kopyalanan her iki listede de çözüm 2a'yı kullanabilirsiniz . Ancak, kopyalama çok pahalıysa bu çekici olmayabilir.

Bu şunlara yol açar:

Seçenek 3

Gereksinimleriniz bölüm 2b ile aynıysa , ancak kopyalama çok pahalıdır. Sıralamayı sizin için yapmak için bir TreeSet kullanabilirsiniz. Her listeyi kendi TreeSet içine dökün. Kümede sıralanacak ve orijinal listeler bozulmadan kalacaktır. Ardından equals()her iki TreeSets için de bir karşılaştırma yapın . TreeSetsS O (nlogn) zaman inşa edilebilir ve equals()O (n) 'dir.

İstediğini al :-).

EDIT: Laurence Gonsalves'in belirttiği uyarıyı neredeyse unutuyordum. TreeSet uygulaması yinelemeleri ortadan kaldıracaktır. Yinelemeleri önemsiyorsanız, bir çeşit sıralı çoklu kümeye ihtiyacınız olacaktır.


Kopyaları önemsiyorsanız, koleksiyonların boyutunun diğer testlerden önce eşit olup olmadığını her zaman test edebilirsiniz.
laz

Daha spesifik olarak, kopyaların olması eşitsizliği gösteriyorsa, herhangi bir eşitlik denetiminin başarılı olma şansı elde etmeden önce listelerin boyutu aynı olmalıdır.
laz

7
@laz: iki listede farklı öğeler kopyalanırsa boyutun denetlenmesi çalışmaz. örneğin: [A, A, B] ve [A, B, B] eşit büyüklüktedir.
Laurence Gonsalves

@Laurence: laz'ın gönderisinin biraz kafa karıştırıcı olduğuna katılıyorum (anlamadan önce birkaç kez okudum). Ben sadece 2 koşul geçerli olduğunda özel durum için bir "kısayol" sağlamaya çalıştığını düşünüyorum: (1) kopyaları önemlidir ve (2) liste boyutları farklıdır. Örneğinizde, bence laz, tartıştığımız tüm aynı kontrolleri yapmak gerektiğini hala söylüyor. (En azından ben böyle okuyorum). Kopyalar ÖNEMLİ değilse, boyutu özel bir durum kontrolü olarak kullanamazsınız. Ancak 2 koşul geçerli olduğunda, sadece "list1.size ()! = List2.size ()) yanlış döndürürse" diyebilirsiniz.
Tom

9
Tüm yanlış cevapları vermeyi düşünürüm, her iki yolu da içermeniz gerekir. a.containsAll(b) && b.containsAll(a)
Richard Tingle

24

Kullanmakta (veya kullanmak için mutluyuz) Apache Commons Koleksiyonları ediyorsanız kullanabilirsiniz CollectionUtils.isEqualCollection hangi "Verilen Koleksiyonları IFF gerçek döner tam olarak aynı kardinallikleri ile tam olarak aynı öğeler içerir."


Çok güzel hashmap tabanlı uygulama. Çalışma zamanı O (n) olmalıdır ve çok sayıda yinelenen öğe varsa, takip etmek için minimum bellek kullanır (temel olarak her koleksiyon için bir harita kullanarak öğelerin frekansını (kardinalite) izler). Dezavantajı ek bir O (n) bellek kullanımına sahip olmasıdır.
Muhd

17

Partiye çok geç ama bu boş güvenli çeki eklemek istedim:

Objects.equals(list1, list2)

8

Bu eski bir iş parçacığı olduğunu biliyorum, ama diğer cevapların hiçbiri benim kullanım davasını tamamen çözdü (Guava Multiset aynı şeyi yapabilir, ama burada bir örnek yok). Lütfen biçimlendirmemi affedin. Hala yığın borsasında yayınlama konusunda yeniyim. Ayrıca herhangi bir hata olup olmadığını bana bildirin

Diyelim ki List<T>a ve List<T>b'ye sahip olduğunuzu ve bunların aşağıdaki koşullara eşit olup olmadığını kontrol etmek istediğinizi varsayalım:

1) O (n) beklenen çalışma süresi
2) Eşitlik şu şekilde tanımlanır: a veya b'deki tüm öğeler için, öğenin a'da meydana gelme sayısı, öğenin b'de gerçekleşme sayısına eşittir. Eleman eşitliği T.equals () olarak tanımlanır

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;
}

Çalışma süresi O (n) 'dir çünkü bir hashmap içine O (2 * n) yerleştirmeler yapıyoruz ve O (3 * n) hashmap seçimleri yapıyoruz. Bu kodu tamamen test etmedim, bu yüzden dikkatli olun :)

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

5

Siparişin aynı olmasını gerektirmeyen, ancak aynı değerin birden fazlasına sahip olmayı destekleyen bu sürümü deneyin. Yalnızca her biri herhangi bir değerde aynı miktarda olduğunda eşleşirler.

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

work.remove (eleman) O (n), bu nedenle bu çözüm O (n ^ 2)
Andrew

Veya aynı türden O (n1 * n2)
Lee Meador

Aynı senaryoyu da kullandım çünkü tüm senaryoları ele alıyor ve koleksiyon boyutu O (n ^ 2) önemli değil o kadar büyük değil
Naresh Joshi

3

Liste'deki eşittir yöntemi bunu yapar, Listeler sıralanır, böylece iki tane olmak için Listelerin aynı sırada aynı öğeleri olması gerekir.

return list1.equals(list2);

3
Listeler siz sıralamadığınız sürece sıralanmaz.
Michael Myers

Myself @ nefes. Çok açık bir cevap. Artık bir web sayfasını Ctrl + F bile yapamayacağınız bir günün çok uzun sürdüğünü biliyorsunuz. :)
Grundlefleck

2
@mmyers: listelemediğiniz öğeler , siz onları sıralamadığınız sürece sıralanmaz. Listelerin kendileri, listedeki öğeleri değiştirmedikçe değişmeyen örtük bir öğe sırasına (dizine göre) sahiptir. (iki kez tekrarlarsanız tutarlı bir sipariş garantisinin olmadığı Setler veya Koleksiyonlar)
Jason S

Listelerin sıralandığını söyleyerek daveb'in ne anlama geldiğini düşünüyorum.List.equals eşitliği belirlemek için öğelerin sırasını dikkate alır. Javadoc'a bakın.
laz

2
Demek istediğim, {"A", "B"} içeren bir liste ile {"B", "A"} içeren bir listenin bu yöntemle eşit olmamasıdır. Amaçlanan bu çok iyi olabilir, ama kimsenin göz ardı etmediğinden emin olmak istedim.
Michael Myers

3

İki listenin aynı öğelere, ancak farklı sıraya sahip olduğu durumlar için çözüm:

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

2
Sanırım listeyi kopyalamanıza gerek yok.
AjahnCharles

1
Bunun removeAll()yerine neden kullandığınızı da not etmek isteyebilirsiniz containsAll()(benim anlayışım listTwo listOne içinde yalnızca bir kez bulunan kopyalar içeriyorsa, includeAll () yaklaşımının listeleri yanlış olarak eşit olarak bildirmesi).
AjahnCharles

3

Tom'un cevabı mükemmel, cevaplarına tamamen katılıyorum!

Bu sorunun ilginç bir yönü, Listtürün kendisine ve onun doğal düzenine ihtiyacınız olup olmadığıdır .

Değilse, Iterable veya Collectionsize yerine kontrol etmek istediğiniz zaman ekleme zamanında sıralanır veri yapılarını etrafında geçen bazı esneklik tanıyor.

Sipariş hiç önemli değilse (ve yinelenen öğeleriniz yoksa) Set .

Sipariş LinkedHashSetönemliyse, ekleme zamanı ile tanımlanmışsa (ve yinelemeleriniz yoksa) TreeSet'e benzeyen ancak ekleme süresine göre sıralananı (yinelemeler sayılmaz) düşünün . Bu aynı zamanda size O(1)amortismana tabi erişim sağlar O(log n).


2

Basit kod:

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;
}

2

Laurence'ın cevabına ek olarak, bunu da null-güvenli yapmak istiyorsanız:

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

1
if (list1 == null) return list2==null; if (list2 == null) return false;
Çekleri

Listeler [a, a, b, c] & [a, b, c] ise ve listelerin boyutunun aynı olduğundan emin olmak için ek bir kontrol eklenmediği sürece çalışmaz.
Venkat Madhav

2
list1.equals(list2);

Listeniz özel bir Sınıf Sınıfım içeriyorsa, bu sınıfın equalsişlevi geçersiz kılması gerekir .

 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;
        }
  }

Not: a yerine bir java.util.Set üzerinde eşitlikleri test etmek istiyorsanız java.util.List, nesnenizin hashCode işlevi geçersiz kılması gerekir .


1
satır şu ise: return this.field == MyClass.class.cast (diğer); this.field == MyClass.class.cast (diğer) .field;
alpere

@alpere oh! haklısın ! Ben tamir edeceğim. Teşekkürler !
Pierre


0

Apache'nin org.apache.commons.collections kütüphanesini kullanabilirsiniz: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

Bu ayrıca liste öğelerinin aynı sırada olmasını gerektirir.
josh-cain

listeyi karşılaştırmadan önce sıralayabilirsiniz
David Zhao

Elbette, listede saklanan veya sıralanabilir (veya bir karşılaştırıcı ayarladınız) türlerini yapabilirsiniz. Ancak Apache uygulama algoritması, statik olmak dışında normal list1.equals (list2) öğesinden farklı değildir. Soruyu nerede yanlış anladığımı görüyorum ve aslında liste öğelerinin aynı sırayla nasıl karşılaştırılacağını soruyordu. Benim hatam!
josh-cain

@DavidZhao: bağlantı öldü.
Aniket Kulkarni


0

Her iki listenin de boş olup olmadığını kontrol edin. Boyutları farklıysa, bu listeler eşit değildir. Listelerin anahtar olarak elemanlarından ve tekrar olarak değerlerden oluşan haritalar oluşturun ve haritaları karşılaştırın.

Varsayımlar, eğer her iki liste de boşsa, ben onları eşit sayıyorum.

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;
}

Bu nesneler için yöntem eşitlerinin düzgün bir şekilde tanımlanması gerektiğini lütfen unutmayın. https://stackoverflow.com/a/24814634/4587961


1
Bir öğenin her listede farklı sayıda bulunamayacağını varsaydınız; örneğin, [x, x, y]vs [x, y, y]uygulamanızla birlikte doğru dönecektir.
AjahnCharles

@CodeConfident, çok teşekkür ederim! Cevabı güncelledim. Bir mao kullanacağım!
Yan Khonski

-2

Hangi somut Liste sınıfını kullandığınıza bağlıdır. AbstractCollection soyut sınıfı, başka bir koleksiyon alan (List bir koleksiyon) ve includeAll (Koleksiyon) adında bir yönteme sahiptir ve:

Bu koleksiyon belirtilen koleksiyondaki tüm öğeleri içeriyorsa true değerini döndürür.

Dolayısıyla, bir ArrayList iletiliyorsa, tam olarak aynı olup olmadıklarını görmek için bu yöntemi çağırabilirsiniz.

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

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

    foo.containsAll(bar);

İncludeAll () öğesinin nedeni, ikinci listede eşleşmeyi arayan ilk listede yinelenmesidir. Yani eğer bozuklarsa eşittir () onu almayacaktır.

EDIT: Ben sadece burada sunulan çeşitli seçenekleri gerçekleştirme amortisman çalışma süresi hakkında yorum yapmak istiyorum. Çalışma süresi önemli mi? Elbette. Düşünmeniz gereken tek şey bu mu? Hayır.

HER tek öğeyi listelerinizden diğer listelere kopyalamanın maliyeti zaman alır ve ayrıca iyi bir bellek alanı kullanır (kullandığınız belleği etkili bir şekilde iki katına çıkarır).

Bu nedenle, JVM'nizdeki bellek bir endişe değilse (genellikle olması gerekir), o zaman yine de her öğeyi iki listeden iki TreeSet'e kopyalamak için gereken süreyi göz önünde bulundurmanız gerekir. Her öğeyi girerken sıraladığını unutmayın.

Son tavsiyem? Burada iyi bir karar verebilmeniz için veri kümenizi ve veri kümenizde kaç öğe olduğunu ve veri kümenizdeki her nesnenin ne kadar büyük olduğunu göz önünde bulundurmanız gerekir. Onlarla oynayın, her şekilde bir tane oluşturun ve hangisinin daha hızlı çalıştığını görün. İyi bir egzersiz.


2
Foo.containsAll (bar) && bar.containsAll (foo) olmak zorunda olmaz; ?
Carl Manaster

Hayır, foo içindeki her öğeden geçer ve çubuğun bu öğeyi içerip içermediğini görür. Daha sonra uzunluğun iki listeyle aynı olmasını sağlar. Her foo için çubukta foo.element == bar.element ve foo.length == bar.length olacak şekilde bir öğe varsa, bunlar aynı öğeleri içerir.
amischiefr

verimlilik garantisi olup olmadığını biliyor muyuz? yoksa bu tipik olarak O (n ^ 2) midir?
Tom

Eşleşen bir eleman arayarak yinelenen diğer diziler gibi en kötü durum çalışma süresi O (n ^ 2) olacaktır. Bu durumda, uygulama gerçekten de eşleşme arayan bir seferde bir öğe üzerinden yineleniyor gibi görünüyor. İtfa edilmiş çalışma süresinde spekülasyon yapmayacağım, ama evet en kötü durum O (n ^ 2).
amischiefr

1
Bu işe yaramaz: {1,2,2} .containsAll ({1,1,2}) ve tersi ve iki liste aynı boyuta sahip.
comco
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.