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


102

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

例えば。

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

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

ありがとう

シェカール


7
比較ロジックを知らない(および変更しない)ループを最適化することはできません。あなたのコードをもっと見せてもらえますか?
josefx 2010

回答:


161
firstSet.equals(secondSet)

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

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

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

1つのセットにあり、他のセットにはない要素を取得する必要がある場合。
編集:set.removeAll(otherSet)セットではなくブール値を返します。removeAll()を使用するには、セットをコピーして使用する必要があります。

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

内容場合onetwo両方とも空になっている場合、次の2つのセットが同等であったことを知っています。そうでない場合は、セットを不平等にする要素があります。

レコード数が多くなる可能性があるとのことですが、基礎となる実装がaの場合、HashSet各レコードのフェッチはO(1)時間内に行われるため、実際にはそれ以上の効果は得られません。TreeSetですO(log n)


3
Setでequals()を呼び出す場合、Recordクラスのequals()およびhashcode()の実装も同様に重要です。
Vineet Reynolds、2010

1
removeAll()の例が正しいかどうかはわかりません。removeAll()は、別のセットではなくブール値を返します。secondSetの要素は実際にはfirstSetから削除され、変更が行われた場合はtrueが返されます。
Richard Corfield 2012年

4
まだコピーを作成していないため、removeAllの例は正しくありません(1つを設定= firstSet、2つを設定= secondSet)。コピーコンストラクタを使用します。
Michael Rusch 2013年

1
実際、のデフォルトの実装は、最悪の場合のequals2回の呼び出しよりも高速containsAllです。私の答えを見てください。
Stephen C

6
Set one = new HashSet(firstSet)を実行する必要があります。そうしないと、firstSetとsecondSetのアイテムが削除されます。
Bonton255 2017年

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

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

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

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

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

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


更新

カスタムセットの実装に時間を費やすことをいとわないのであれば、「ほぼ同じ」ケースを改善できるアプローチがあります。

アイデアは、セット全体のハッシュを事前に計算してキャッシュし、セットの現在のハッシュコード値をで取得できるようにする必要があるというものです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 -N時間に1回だけ間違った答えを与えます。Nを十分に大きく(たとえば512ビット)すると、間違った答えの確率は無視できます(たとえば、およそ10 -150)。

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

もう1つの欠点は、確率がどれほど小さくても、ゼロ以外のエラーの確率は許容できない可能性があることです。(しかし、そうである場合...宇宙線が重要なビットをフリップするケースにどう対処しますか?それとも、冗長システムの2つのインスタンスで同じビットを同時にフリップする場合?)


(checksumsDoNotMatch(0))がfalseを返す場合は、そうする必要があります。それ以外の場合は、doHeavyComparisonToMakeSureTheSetsReallyMatch(o);を返します。
Esko Piirainen、

必ずしも。2つのチェックサムが等しくないセットと一致する確率が十分に小さい場合は、比較をスキップできると考えます。計算する。
スティーブンC

17

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インターフェースAbstractSet
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に配置します。このようにして、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 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)
Alex
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.