Java'da iki grubu karşılaştırmanın en hızlı yolu nedir?


103

Listenin öğelerini karşılaştıran bir kod parçasını optimize etmeye çalışıyorum.

Örneğin.

public void compare(Set<Record> firstSet, Set<Record> secondSet){
    for(Record firstRecord : firstSet){
        for(Record secondRecord : secondSet){
            // comparing logic
        }
    }
}

Lütfen setlerdeki kayıt sayısının yüksek olacağını dikkate alın.

Teşekkürler

Shekhar


7
Karşılaştırma mantığını bilmeden (ve değiştirmeden) döngüleri optimize etmek mümkün değildir. Kodunuzun daha fazlasını gösterebilir misiniz?
josefx

Yanıtlar:


161
firstSet.equals(secondSet)

Gerçekten karşılaştırma mantığında ne yapmak istediğinize bağlıdır ... yani, bir kümede diğerinde olmayan bir öğe bulursanız ne olur? Yönteminizin bir voiddönüş türü vardır, bu nedenle bu yöntemde gerekli işi yapacağınızı varsayıyorum.

İhtiyacınız olursa daha hassas kontrol:

if (!firstSet.containsAll(secondSet)) {
  // do something if needs be
}
if (!secondSet.containsAll(firstSet)) {
  // do something if needs be
}

Bir sette olan ve diğerinde olmayan öğeleri almanız gerekiyorsa.
EDIT: set.removeAll(otherSet)bir küme değil, bir boole döndürür. RemoveAll () 'ı kullanmak için seti kopyalayıp kullanmanız gerekir.

Set one = new HashSet<>(firstSet);
Set two = new HashSet<>(secondSet);
one.removeAll(secondSet);
two.removeAll(firstSet);

İçeriği oneve twoher ikisi de boşsa, iki kümenin eşit olduğunu bilirsiniz. Değilse, kümeleri eşitsiz yapan unsurlara sahipsiniz demektir.

Kayıt sayısının yüksek olabileceğinden bahsettiniz. Altta yatan uygulama bir ise, HashSeto zaman her kaydın getirilmesi O(1)zamanında yapılır , bu yüzden bundan çok daha fazlasını elde edemezsiniz. TreeSetolduğunu O(log n).


3
Record sınıfı için equals () ve hashcode () uygulaması, Set'te equals () çağrılırken eşit derecede önemlidir.
Vineet Reynolds

1
RemoveAll () örneklerinin doğru olduğundan emin değilim. removeAll (), başka bir Set değil, bir boole döndürür. SecondSet'teki öğeler aslında firstSet'ten kaldırılır ve bir değişiklik yapıldıysa true döndürülür.
Richard Corfield

4
RemoveAll örneği, kopya oluşturmadığınız için hala doğru değildir (Set one = firstSet; Set two = secondSet). Kopya oluşturucuyu kullanırdım.
Michael Rusch

1
Aslında, varsayılan uygulaması en kötü durumda equalsiki çağrıdan daha hızlıdır containsAll; cevabımı gör.
Stephen C

6
Set one = new HashSet (firstSet) yapmanız gerekir, aksi takdirde firstSet ve secondSet'teki öğeler kaldırılır.
Bonton255

61

Sadece setlerin eşit olup olmadığını bilmek istiyorsanız, equalsyöntem AbstractSetaşağıdaki gibi kabaca uygulanır:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return containsAll(c);
    }

Aşağıdaki durumlarda yaygın durumları nasıl optimize ettiğini not edin:

  • iki nesne aynı
  • diğer nesne hiç bir küme değildir ve
  • iki setin boyutları farklıdır.

Bundan sonra , diğer kümede de bu kümede olmayan bir öğe bulur bulmaz containsAll(...)dönecektir false. Ancak tüm öğeler her iki sette de mevcutsa, hepsini test etmesi gerekecektir.

Bu nedenle en kötü durum performansı, iki küme eşit olduğunda ancak aynı nesneler olmadığında ortaya çıkar. Bu maliyet tipik olarak O(N)veya O(NlogN)uygulanmasına bağlıdır this.containsAll(c).

Ve setler büyükse ve elementlerin sadece küçük bir yüzdesinde farklılık gösteriyorsa, en kötü duruma yakın performans elde edersiniz.


GÜNCELLEME

Özel bir set uygulamasına zaman ayırmaya istekliysen, "neredeyse aynı" durumu iyileştirebilecek bir yaklaşım var.

Buradaki fikir, setin mevcut hashcode değerini içinde alabilmeniz için tüm set için bir hash'i önceden hesaplamanız ve önbelleğe almanız gerektiğidir O(1). Daha sonra iki setin hashcode'unu ivme olarak karşılaştırabilirsiniz.

Böyle bir karma kodu nasıl uygulayabilirsin? Peki, set hashcode şöyle olsaydı:

  • boş bir küme için sıfır ve
  • Boş olmayan bir küme için tüm öğe karma kodlarının XOR'u,

daha sonra, bir öğeyi her eklediğinizde veya çıkardığınızda kümenin önbelleğe alınmış karma kodunu ucuza güncelleyebilirsiniz. Her iki durumda da, elemanın hashcode'unu mevcut set hashcode ile XOR.

Tabii ki, bu, eleman karma kodlarının kararlı olduğunu ve elemanların kümelerin üyesi olduğunu varsayar. Ayrıca, öğe sınıflarının hashcode işlevinin iyi bir yayılma sağladığını varsayar. Bunun nedeni, iki set hashcode aynı olduğunda, yine de O(N)tüm öğelerin karşılaştırmasına geri dönmeniz gerektiğidir .


Bu fikri biraz daha ileri götürebilirsin ... en azından teoride.

UYARI - Bu oldukça spekülatiftir. İsterseniz bir "düşünce deneyi".

Set element sınıfınızın, element için bir kripto sağlama toplamı döndürmek için bir metodu olduğunu varsayalım. Şimdi, öğeler için döndürülen sağlama toplamlarını XORing yaparak kümenin sağlama toplamlarını uygulayın.

Bu bize ne kazandırır?

Biz hiçbir şeyin sinsi oluyor varsayalım buysa, herhangi iki eşitsiz seti elemanları aynı N-bitlik sağlama olması ihtimali 2 -N . Ve olasılık 2 eşit olmayan setleri de 2 aynı N-bit sağlama almaktadır sahip -N . Yani benim fikrim şu şekilde uygulayabilirsiniz equals:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return checksums.equals(c.checksums);
    }

Yukarıdaki varsayımlar altında, bu size yalnızca 2- N seferinde bir kez yanlış cevap verecektir . N'yi yeterince büyük yaparsanız (ör. 512 bit), yanlış yanıt olasılığı önemsiz hale gelir (ör. Kabaca 10-150 ).

Olumsuz yanı, özellikle bit sayısı arttıkça, öğeler için kripto sağlama toplamlarını hesaplamanın çok pahalı olmasıdır. Dolayısıyla, sağlama toplamlarını ezberlemek için gerçekten etkili bir mekanizmaya ihtiyacınız var. Ve bu sorunlu olabilir.

Ve diğer dezavantajı, sıfır olmayan bir hata olasılığının, olasılık ne kadar küçük olursa olsun kabul edilemez olabileceğidir . (Ama durum böyleyse ... kozmik ışının kritik bir biti döndürdüğü durumu nasıl ele alırsınız? Veya yedek bir sistemin iki örneğinde aynı biti aynı anda döndürürse?)


(ChecksumsDoNotMatch (0)) false döndürürse; yoksa doHeavyComparisonToMakeSureTheSetsReallyMatch (o);
Esko Piirainen

Şart değil. Eşit olmayan kümeler için iki sağlama toplamının eşleşme olasılığı yeterince küçükse, karşılaştırmayı atlayabileceğinizi varsayıyorum. Matematik yap.
Stephen C

17

Guava'da Setsburada yardımcı olabilecek bir yöntem var:

public static <E>  boolean equals(Set<? extends E> set1, Set<? extends E> set2){
return Sets.symmetricDifference(set1,set2).isEmpty();
}

5

Aşağıdaki çözüme https://www.mkyong.com/java/java-how-to-compare-two-sets/ adresinden ulaşabilirsiniz.

public static boolean equals(Set<?> set1, Set<?> set2){

    if(set1 == null || set2 ==null){
        return false;
    }

    if(set1.size() != set2.size()){
        return false;
    }

    return set1.containsAll(set2);
}

Veya tek bir return ifadesi kullanmayı tercih ederseniz:

public static boolean equals(Set<?> set1, Set<?> set2){

  return set1 != null 
    && set2 != null 
    && set1.size() == set2.size() 
    && set1.containsAll(set2);
}

Veya , ek boş kontroller haricinde buradaki çözümle hemen hemen aynı olan (JDK ile birlikte gönderilen) equals()yöntemini kullanabilirsiniz . Java-11 Set ArayüzüAbstractSet
Chaithu Narayana

4

Çok özel durumlar için bir O (N) çözümü vardır:

  • setlerin ikisi de sıralanır
  • her ikisi de aynı sırada sıralandı

Aşağıdaki kod, her iki kümenin de karşılaştırılabilir kayıtlara dayandığını varsayar. Benzer bir yöntem bir Karşılaştırıcıya dayalı olabilir.

    public class SortedSetComparitor <Foo extends Comparable<Foo>> 
            implements Comparator<SortedSet<Foo>> {

        @Override
        public int compare( SortedSet<Foo> arg0, SortedSet<Foo> arg1 ) {
            Iterator<Foo> otherRecords = arg1.iterator();
            for (Foo thisRecord : arg0) {
                // Shorter sets sort first.
                if (!otherRecords.hasNext()) return 1;
                int comparison = thisRecord.compareTo(otherRecords.next());
                if (comparison != 0) return comparison;
            }
            // Shorter sets sort first
            if (otherRecords.hasNext()) return -1;
            else return 0;
        }
    }

3

GuavaKitaplık kullanıyorsanız şunları yapmak mümkündür:

        SetView<Record> added = Sets.difference(secondSet, firstSet);
        SetView<Record> removed = Sets.difference(firstSet, secondSet);

Ve sonra bunlara dayanarak bir sonuç çıkarın.


2

SecondSet'i karşılaştırmadan önce bir HashMap'e koyardım. Bu şekilde ikinci listenin arama süresini n (1) 'e indireceksiniz. Bunun gibi:

HashMap<Integer,Record> hm = new HashMap<Integer,Record>(secondSet.size());
int i = 0;
for(Record secondRecord : secondSet){
    hm.put(i,secondRecord);
    i++;
}
for(Record firstRecord : firstSet){
    for(int i=0; i<secondSet.size(); i++){
    //use hm for comparison
    }
}

Veya ikinci liste için bir hashmap yerine dizi kullanabilirsiniz.
Şahin Habeşoğlu

Ve bu çözüm, kümelerin sıralanmadığını varsayar.
Şahin Habesoğlu

1
public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;

        Set<String> a = this;
        Set<String> b = o;
        Set<String> thedifference_a_b = new HashSet<String>(a);


        thedifference_a_b.removeAll(b);
        if(thedifference_a_b.isEmpty() == false) return false;

        Set<String> thedifference_b_a = new HashSet<String>(b);
        thedifference_b_a.removeAll(a);

        if(thedifference_b_a.isEmpty() == false) return false;

        return true;
    }

-1

Eşittir yöntemiyle yöntem referansının kullanılabileceğini düşünüyorum. Şüpheye yer bırakmayan nesne türünün kendi karşılaştırma yöntemine sahip olduğunu varsayıyoruz. Düz ve basit bir örnek burada,

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));

Set<String> set2 = new HashSet<>();
set2.addAll(Arrays.asList("hanks","leo","bale"));

Predicate<Set> pred = set::equals;
boolean result = pred.test(set2);
System.out.println(result);   // true

1
bunu söylemenin karmaşık bir yoluset.equals(set2)
Alex
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.