Какой самый быстрый способ сравнить два набора в Java? - PullRequest
84 голосов
/ 27 июля 2010

Я пытаюсь оптимизировать фрагмент кода, который сравнивает элементы списка.

Например.

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

Пожалуйста, примите во внимание, что количество записей в наборах будет большим.

Спасибо

Шекхар

Ответы [ 9 ]

137 голосов
/ 27 июля 2010
firstSet.equals(secondSet)

Это действительно зависит от того, что вы хотите сделать в логике сравнения ... т.е. что произойдет, если вы найдете элемент в одном наборе, а не в другом? Ваш метод имеет тип возврата void, поэтому я предполагаю, что вы будете выполнять необходимую работу с этим методом.

Более точный контроль, если вам это нужно:

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

Если вам нужно получить элементы, которые находятся в одном наборе, а не в другом.
РЕДАКТИРОВАТЬ: set.removeAll(otherSet) возвращает логическое значение, а не набор. Чтобы использовать removeAll (), вам нужно скопировать набор, а затем использовать его.

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

Если содержимое one и two оба пустые, то вы знаете, что эти два набора были равны. Если нет, то у вас есть элементы, которые сделали наборы неравными.

Вы упомянули, что количество записей может быть большим. Если базовой реализацией является HashSet, то выборка каждой записи выполняется за O(1) время, так что вы не сможете получить намного лучше, чем это. TreeSet - это O(log n).

57 голосов
/ 27 июля 2010

Если вы просто хотите узнать, равны ли наборы, метод equals для AbstractSet реализован примерно так, как показано ниже:

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

Обратите внимание, как оптимизируются общие случаи, когда:

  • два объекта одинаковы
  • другой объект вообще не является множеством, а
  • Размеры двух комплектов разные.

После этого containsAll(...) вернет false, как только найдет элемент в другом наборе, которого также нет в этом наборе. Но если все элементы присутствуют в обоих наборах, необходимо проверить все из них.

Следовательно, наихудший вариант производительности возникает, когда два набора равны, но не совпадают объекты Эта стоимость обычно составляет O(N) или O(NlogN) в зависимости от реализации this.containsAll(c).

И вы получите производительность, близкую к худшему, если наборы велики и отличаются лишь небольшим процентом элементов.


UPDATE

Если вы готовы тратить время на реализацию пользовательского набора, есть подход, который может улучшить «почти такой же» случай.

Идея состоит в том, что вам нужно предварительно рассчитать и кэшировать хеш для всего набора, чтобы вы могли получить текущее значение хеш-кода набора в O(1). Затем вы можете сравнить хэш-код для двух наборов в качестве ускорения.

Как вы могли бы реализовать такой хэш-код? Хорошо, если установлен хэш-код:

  • ноль для пустого набора и
  • XOR всех хеш-кодов элементов для непустого набора,

тогда вы можете дешево обновлять кэшированный хэш-код набора каждый раз, когда добавляете или удаляете элемент. В обоих случаях вы просто XOR хеш-кода элемента с текущим установленным хеш-кодом.

Конечно, это предполагает, что хеш-коды элементов являются стабильными, в то время как элементы являются членами наборов. Также предполагается, что функция hashcode классов элементов дает хороший разброс. Это потому, что, когда два набора хеш-кодов совпадают, вам все равно придется вернуться к O(N) сравнению всех элементов.


Вы могли бы развить эту идею немного дальше ... по крайней мере, в теории.

Предположим, что в вашем классе элементов set есть метод для возврата контрольных сумм шифрования для элемента. Теперь реализуйте контрольные суммы набора, XORing контрольные суммы, возвращенные для элементов.

Что это нас покупает?

Хорошо, если мы предположим, что ничего не происходит, вероятность того, что любые два неравных набора элементов имеют одинаковые N-битные контрольные суммы, равна 2 -N . И вероятность того, что 2 неравных набора имеют одинаковые N-битные контрольные суммы, также составляет 2 -N . Так что моя идея заключается в том, что вы можете реализовать 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);
    }

В соответствии с приведенными выше предположениями, это даст вам неправильный ответ только один раз за 2 -N времени. Если вы сделаете N достаточно большим (например, 512 бит), вероятность неправильного ответа станет незначительной (например, примерно 10 -150 ).

Недостатком является то, что вычисление крипто контрольных сумм для элементов очень дорого, особенно с увеличением количества битов. Таким образом, вам действительно нужен эффективный механизм для запоминания контрольных сумм. И это может быть проблематично.

15 голосов
/ 17 декабря 2014

В Гуаве есть метод Sets, который может помочь здесь:

public static <E>  boolean equals(Set<? extends E> set1, Set<? extends E> set2){
return Sets.symmetricDifference(set1,set2).isEmpty();
}
4 голосов
/ 27 сентября 2018

У вас есть следующее решение от https://www.mkyong.com/java/java-how-to-compare-two-sets/

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

Или, если вы предпочитаете использовать один оператор return:

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

  return set1 != null 
    && set2 != null 
    && set1.size() == set2.size() 
    && set1.containsAll(set2);
}
4 голосов
/ 24 декабря 2014

Существует O (N) решение для очень специфических случаев, когда:

  • оба набора отсортированы
  • оба отсортированы в одном порядке

В следующем коде предполагается, что оба набора основаны на сопоставимых записях.Аналогичный метод может быть основан на компараторе.

    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 голосов
/ 14 октября 2016

Если вы используете библиотеку Guava, это можно сделать:

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

И затем сделать вывод, основываясь на них.

2 голосов
/ 31 марта 2015

Я бы положил второй набор в HashMap перед сравнением. Таким образом, вы уменьшите время поиска во втором списке до n (1). Как это:

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
    }
}
1 голос
/ 29 ноября 2014
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;
    }
0 голосов
/ 07 июня 2017

Я думаю, что можно использовать ссылку на метод с методом equals. Мы предполагаем, что тип объекта без тени сомнения имеет свой собственный метод сравнения. Простой и простой пример здесь,

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...