Java에서 두 세트를 비교하는 가장 빠른 방법은 무엇입니까?


102

목록의 요소를 비교하는 코드를 최적화하려고합니다.

예 :

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

세트의 레코드 수가 많을 것임을 고려하십시오.

감사

셰 카르


7
비교 논리를 알거나 수정하지 않고는 루프를 최적화 할 수 없습니다. 더 많은 코드를 보여줄 수 있습니까?
josefx

답변:


161
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모두 비어있는, 당신은 두 세트가 동일한이라고 알고있다. 그렇지 않다면 세트를 불평등하게 만든 요소가 있습니다.

레코드 수가 많을 수 있다고 언급하셨습니다. 기본 구현이 a HashSet이면 각 레코드 가져 오기가 제 O(1)시간에 완료 되므로 그보다 훨씬 더 나을 수 없습니다. TreeSet입니다 O(log n).


3
Record 클래스에 대한 equals () 및 hashcode () 구현은 Set에서 equals ()를 호출 할 때 똑같이 중요합니다.
Vineet Reynolds

1
removeAll () 예제가 올바른지 잘 모르겠습니다. removeAll ()은 다른 Set이 아닌 부울을 반환합니다. secondSet의 요소는 실제로 firstSet에서 제거되고 변경된 경우 true가 반환됩니다.
Richard Corfield 2012 년

4
removeAll 예제는 복사본을 만들지 않았기 때문에 여전히 옳지 않습니다 (Set one = firstSet; Set two = secondSet). 복사 생성자를 사용합니다.
Michael Rusch

1
실제로의 기본 구현은 최악의 경우 equals두 번의 호출보다 빠릅니다 containsAll. 내 대답을 참조하십시오.
Stephen C

6
Set one = new HashSet (firstSet)을 수행해야합니다. 그렇지 않으면 firstSet 및 secondSet의 항목이 제거됩니다.
Bonton255

61

단순히 세트가 동일한 지 알고 싶다면 equalson 메서드 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).

세트가 크고 요소의 비율이 아주 조금만 다를 경우 최악에 가까운 성능을 얻을 수 있습니다.


최신 정보

사용자 지정 집합 구현에 시간을 투자하려는 경우 "거의 동일한"사례를 개선 할 수있는 접근 방식이 있습니다.

아이디어는 .NET에서 세트의 현재 해시 코드 값을 가져올 수 있도록 전체 세트에 대한 해시를 미리 계산하고 캐시해야한다는 것입니다 O(1). 그런 다음 두 세트의 해시 코드를 가속도로 비교할 수 있습니다.

그런 해시 코드를 어떻게 구현할 수 있습니까? 설정된 해시 코드가 다음과 같으면

  • 빈 세트의 경우 0
  • 비어 있지 않은 세트에 대한 모든 요소 해시 코드의 XOR,

그런 다음 요소를 추가하거나 제거 할 때마다 세트의 캐시 된 해시 코드를 저렴하게 업데이트 할 수 있습니다. 두 경우 모두 현재 설정된 해시 코드로 요소의 해시 코드를 XOR하기 만하면됩니다.

물론 이것은 요소 해시 코드가 안정적이고 요소가 집합의 구성원이라고 가정합니다. 또한 요소 클래스 해시 코드 함수가 좋은 확산을 제공한다고 가정합니다. 두 세트의 해시 코드가 동일 할 O(N)때 모든 요소 의 비교로 돌아 가야하기 때문 입니다.


적어도 이론 상으로는이 아이디어를 조금 더 발전시킬 수 있습니다.

경고 -이것은 매우 추측 적입니다. 당신이 원한다면 "생각 실험".

set 요소 클래스에 요소에 대한 암호화 체크섬을 반환하는 메서드가 있다고 가정합니다. 이제 요소에 대해 리턴 된 체크섬을 XOR하여 세트의 체크섬을 구현하십시오.

이것이 우리에게 무엇을 사나요?

음, 아무 일도 일어나지 않는다고 가정하면 두 개의 같지 않은 집합 요소가 동일한 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 ).

단점은 요소에 대한 암호화 체크섬을 계산하는 데 특히 비트 수가 증가함에 따라 매우 비싸다는 것입니다. 따라서 체크섬을 메모하기위한 효과적인 메커니즘이 정말로 필요합니다. 그리고 그것은 문제가 될 수 있습니다.

또 다른 단점은 확률이 아무리 작아도 0이 아닌 오류 확률은 허용되지 않을 수 있다는 것 입니다. (하지만 그렇다면 ... 우주 광선이 임계 비트를 뒤집는 경우를 어떻게 처리합니까? 아니면 중복 시스템의 두 인스턴스에서 동일한 비트를 동시에 뒤집는 경우 어떻게합니까?)


(checksumsDoNotMatch (0))가 false를 반환하는 경우 여야합니다. 그렇지 않으면 doHeavyComparisonToMakeSureTheSetsReallyMatch (o);
Esko Piirainen

반드시 그런 것은 아닙니다. 같지 않은 세트에 대해 두 개의 체크섬이 일치 할 확률이 충분히 작 으면 비교를 건너 뛸 수 있습니다. 수학을하십시오.
Stephen C

17

Guava에는 다음과 같은 방법 Sets이 있습니다.

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

5

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

또는 추가 null 검사를 제외하고 여기의 솔루션과 거의 동일한 (JDK와 함께 제공되는) equals()메서드를 사용할 수도 있습니다 . Java-11 Set InterfaceAbstractSet
Chaithu Narayana

4

다음과 같은 매우 특정한 경우를위한 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

Guava라이브러리 를 사용하는 경우 다음 을 수행 할 수 있습니다.

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

그리고 이것들을 바탕으로 결론을 내리십시오.


2

비교하기 전에 secondSet을 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
    }
}

또는 두 번째 목록에 해시 맵 대신 배열을 사용할 수 있습니다.
Sahin Habesoglu 2015 년

그리고이 솔루션은 세트가 정렬되지 않았다고 가정합니다.
Sahin Habesoglu

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

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

1
이 말을 복잡한 방법입니다set.equals(set2)
알렉스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.