web-dev-qa-db-ja.com

Javaの2つのセットを比較する最速の方法は何ですか?

リストの要素を比較するコードを最適化しようとしています。

例えば。

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

セット内のレコード数が多くなることを考慮してください。

ありがとう

シェカル

87
Shekhar
firstSet.equals(secondSet)

それは本当に比較ロジックで何をしたいかに依存します。つまり、一方のセットにある要素をもう一方のセットにない場合はどうなりますか?メソッドにはvoid戻り型があるため、このメソッドで必要な作業を行うことを想定しています。

必要に応じて、よりきめ細かな制御:

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

一方のセットにあり、もう一方のセットにはない要素を取得する必要がある場合。
EDIT:set.removeAll(otherSet)は、セットではなくブール値を返します。 removeAll()を使用するには、セットをコピーしてから使用する必要があります。

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

onetwoの内容が両方とも空の場合、2つのセットが等しいことがわかります。そうでない場合は、セットを不均等にする要素があります。

あなたは、レコードの数が多いかもしれないと述べました。基礎となる実装がHashSetである場合、各レコードのフェッチはO(1)時間で行われるため、それよりもはるかに良くなることはできません。 TreeSetO(log n)です。

141
Noel M

セットが等しいかどうかだけを知りたい場合、equalsAbstractSetメソッドは、おおよそ次のように実装されます。

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

以下の一般的なケースを最適化する方法に注意してください。

  • 2つのオブジェクトは同じです
  • 他のオブジェクトはまったくセットではありません。
  • 2つのセットのサイズは異なります。

その後、containsAll(...)は、このセットにも含まれていない他のセットの要素を見つけるとすぐにfalseを返します。ただし、両方のセットにすべての要素が存在する場合は、それらすべてをテストする必要があります。

したがって、最悪の場合のパフォーマンスは、2つのセットが等しいが同じオブジェクトではない場合に発生します。そのコストは、O(N)の実装に応じて、通常O(NlogN)またはthis.containsAll(c)です。

また、セットが大きく、要素のわずかな割合でのみ異なる場合、最悪のケースに近いパフォーマンスが得られます。


UPDATE

カスタムセットの実装に時間をかけたい場合は、「ほぼ同じ」ケースを改善できるアプローチがあります。

アイデアは、セットの現在のハッシュコード値をO(1)で取得できるように、セット全体のハッシュを事前に計算してキャッシュする必要があるということです。次に、2つのセットのハッシュコードを加速として比較できます。

そのようなハッシュコードをどのように実装できますか?設定されたハッシュコードが次の場合:

  • 空のセットの場合はゼロ、および
  • 空でないセットのすべての要素ハッシュコードのXOR

その後、要素を追加または削除するたびに、セットのキャッシュされたハッシュコードを安価に更新できます。どちらの場合も、要素のハッシュコードと現在のセットのハッシュコードを単にXORします。

もちろん、これは、要素がセットのメンバーである間、要素のハッシュコードが安定していることを前提としています。また、要素クラスのハッシュコード関数が適切な広がりを与えると想定しています。これは、2つのセットのハッシュコードが同じ場合でも、すべての要素のO(N)比較にフォールバックする必要があるためです。


少なくとも理論的には、この考えをもう少し進めることができます。

セット要素クラスに、要素の暗号チェックサムを返すメソッドがあると仮定します。次に、要素に対して返されたチェックサムをXORすることにより、セットのチェックサムを実装します。

これで何が買われますか?

さて、アンダーハンドが発生していないと仮定すると、2つの等しくないセット要素が同じ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回に1回だけ間違った答えを与えます-N 時間。 Nを十分に大きくすると(例:512ビット)、間違った回答の確率は無視できるようになります(例:およそ10-150)。

マイナス面は、要素の暗号チェックサムの計算は、特にビット数が増えるにつれて非常に高価になることです。したがって、チェックサムをメモするための効果的なメカニズムが本当に必要です。そして、それは問題になる可能性があります。

58
Stephen C

グアバSetsには、ここで役立つメソッドがあります:

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

次のような非常に特殊なケースには、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;
        }
    }
4
Philip Couling

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
ilopezluna

Guavaライブラリを使用している場合、次のことが可能です。

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

そして、これらに基づいて結論を出します。

3
riwnodennyk

比較する前に、secondSetをHashMapに配置します。この方法で、2番目のリストの検索時間を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
    }
}
2
Sahin Habesoglu
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
Zahran