Каков наилучший способ получить симметричную разницу между двумя наборами в java?

Мне интересно, есть ли быстрый/чистый способ получить симметричную разницу между двумя наборами?

Я имею:

Set<String> s1 = new HashSet<String>();
s1.add("a");
s1.add("b");
s1.add("c");

Set<String> s2 = new HashSet<String>();
s2.add("b");

Мне нужно что-то вроде:

Set<String> diff = Something.diff(s1, s2);
// diff would contain ["a", "c"]

Просто чтобы уточнить, мне нужна симметричная разница.

Ответ 1

Вы можете использовать некоторые функции из библиотеки Google Guava (что действительно здорово, я настоятельно рекомендую!):

Sets.difference(s1, s2);
Sets.symmetricDifference(s1, s2);

Javadocs для diff () и симметричногоDifference()

symmetricDifference() делает именно то, что вы просите, но difference() также часто бывает полезна.

Оба метода возвращают .immutableCopy() представление, но вы можете, например, вызвать .immutableCopy() для результирующего набора, чтобы получить неизменяемый набор. Если вы не хотите представления, но вам нужен установленный экземпляр, который вы можете изменить, вызовите .copyInto(s3). Смотрите SetView для этих методов.

Ответ 2

Вы хотите симметричную разницу.

public static <T> Set<T> diff(final Set<? extends T> s1, final Set<? extends T> s2) {
    Set<T> symmetricDiff = new HashSet<T>(s1);
    symmetricDiff.addAll(s2);
    Set<T> tmp = new HashSet<T>(s1);
    tmp.retainAll(s2);
    symmetricDiff.removeAll(tmp);
    return symmetricDiff;
}

Если вам нужна библиотека, Apache Commons CollectionUtils имеет

CollectionUtils.disjunction(s1, s2)

который возвращает не общий Collection.

и Наборы Guava имеют

Sets.symmetricDifference(s1, s2)

который возвращает немодифицируемый Set как общий Sets.SetView.

Гуава немного более современна, поддерживает дженерики, но любой из них будет работать.

Ответ 3

Если вы можете использовать Apache-Commons Collections, вы ищете CollectionUtils.disjunction(Collection a, Collection b). Он возвращает симметричную разницу обоих коллекций.

Если нет, выведите (removeAll) пересечение (retainAll) обоих множеств с объединением обоих (addAll):

Set<String> intersection = new HashSet<String>(set1);
intersection.retainAll(set2);

Set<String> difference = new HashSet<String>();
difference.addAll(set1);
difference.addAll(set2);
difference.removeAll(intersection);

Ответ 4

Прокрутите один набор и сравните.

Это только O(n) для прокрутки одного из множеств. Рассмотрим этот код:

for (String key: oldSet) {
    if (newSet.contains(key))
        newSet.remove(key);
    else
        newSet.add(key);
}

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

Ответ 5

public class Practice {
    public static void main(String[] args) {
        Set<Integer> set1 = new HashSet<Integer>();
        Set<Integer> set2 = new HashSet<Integer>();
        set1.add(1);
        set1.add(4);
        set1.add(7);
        set1.add(9);

        set2.add(2);
        set2.add(4);
        set2.add(5);
        set2.add(6);
        set2.add(7);

        symmetricSetDifference(set1, set2);
    }

    public static void symmetricSetDifference(Set<Integer>set1, Set<Integer>set2){
        //creating a new set
        Set<Integer> newSet = new HashSet<Integer>(set1);
        newSet.removeAll(set2);
        set2.removeAll(set1);
        newSet.addAll(set2);
        System.out.println(newSet);
    }

}

Ответ 6

Решение Java 8

Мы можем написать два служебных метода (для Java 8 и ранее) в некотором классе SetUtils (say) как:

public static <T> Set<T> symmetricDifferenceJava8(final Set<T> setOne, final Set<T> setTwo) {
    Set<T> result = new HashSet<>(setOne);
    setTwo.stream().filter(not(resultSet::add)).forEach(resultSet::remove);
    return result;
}

public static <T> Set<T> symmetricDifference(final Set<T> setOne, final Set<T> setTwo) {
    Set<T> result = new HashSet<T>(setOne);
    for (T element : setTwo) {
        if (!result.add(element)) {
            result.remove(element);
        }
    }
    return result;
}

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

Метод add возвращает false, если элемент уже существует, а метод negate используется для отрицания предиката.

Java 11

У нас есть метод Predicate # not для предиката в Java 11, и мы можем использовать его как:

public static <T> Set<T> symmetricDifferenceJava11(final Set<T> setOne, final Set<T> setTwo) {
    Set<T> result = new HashSet<>(setOne);
    setTwo.stream().filter(Predicate.not(resultSet::add)).forEach(resultSet::remove);
    return result;
}

Ответ 7

s1.addAll(s2);
s1.removeAll(s2);

Должен работать.