Java ArrayList-2つのリストが等しいかどうか、順序は関係ないかどうかを確認するにはどうすればよいですか?


133

私は2つArrayListのタイプのs Answer(自作クラス)を持っています。

2つのリストを比較して、同じ内容が含まれているかどうかを確認しますが、順序は関係ありません。

例:

//These should be equal.
ArrayList<String> listA = {"a", "b", "c"}
ArrayList<String> listB = {"b", "c", "a"}

List.equalsは、同じサイズ、内容、および要素の順序が含まれている場合、2つのリストは等しいと述べています。私は同じことを望みますが、順序は関係ありません。

これを行う簡単な方法はありますか?または、ネストされたforループを実行して、両方のリストの各インデックスを手動で確認する必要がありますか?

注:ArrayList別のタイプのリストに変更することはできません。そのままにする必要があります。


4
この質問の回答を参照してください:stackoverflow.com/a/1075699/1133011
David Kroukamp

JavaのList.containsAll(list)を参照してください
Sahil Jain

回答:


130

を使用Collections.sort()して両方のリストをソートしてから、equalsメソッドを使用できます。少し良い解決策は、注文する前に最初に同じ長さであるかどうかを確認し、そうでない場合、それらが等しくない場合、次に並べ替えてから、等しいを使用することです。たとえば、文字列のリストが2つある場合、次のようになります。

public  boolean equalLists(List<String> one, List<String> two){     
    if (one == null && two == null){
        return true;
    }

    if((one == null && two != null) 
      || one != null && two == null
      || one.size() != two.size()){
        return false;
    }

    //to avoid messing the order of the lists we will use a copy
    //as noted in comments by A. R. S.
    one = new ArrayList<String>(one); 
    two = new ArrayList<String>(two);   

    Collections.sort(one);
    Collections.sort(two);      
    return one.equals(two);
}

20
元のリストの順序を破壊しないように(そうするように)忘れないでくださいCollections.sort
arshajii 2012年

@ARSはい、それは明確な副作用ですが、それが彼らの特定のケースで重要な場合に限ります。
Jacob Schoen

3
one = new ArrayList<String>(one); two = new ArrayList<String>(two);引数を台無しにしないように追加することもできます。
arshajii 2012年

@jschoen Collections.sort()を実行しようとすると、次のエラーが発生します。バインドの不一致:コレクション型のジェネリックメソッドsort(List <T>)は、引数(ArrayList <Answer>)には適用できません。推論された型Answerは、境界パラメータ<T extends Comparable <?スーパーT >>
iaacp

3
関数内の2番目の「if」ステートメントif(one == null || two == null || one.size() != two.size()){ return false; }は、1と2の両方がnullかどうかをすでにチェックしているため、簡略化できます
Hugo

151

おそらく、どのリストにとって最も簡単な方法は次のとおりです。

listA.containsAll(listB) && listB.containsAll(listA)

5
その中での楽しみはどこですか。真剣に言えば、これはおそらくより良い解決策です。
Jacob Schoen

67
[a, b, c][c, b, a, b]が同じ内容であると見なされるかどうかによって異なります。この答えはそれらがそうであると言うでしょう、しかしそれはOPのためにそれらがそうではないかもしれません(1つは重複を含み、他は含まないので)。効率の問題は言うまでもありません。
yshavit 2012年

4
コメントに基づく拡張-System.out.println(((l1.size()== l2.size())&& l2.containsAll(l1)&& l1.containsAll(l2))));
Nrj 2015

8
@Nrj、[1,2,2]と[2,1,1]はどうですか?
ROMANIA_engineer 2015

3
このアプローチはO(n ^ 2)の複雑さを持っています。例として[1,2,3]と[3,2,1]の2つのリストを逆順に考えてみましょう。最初の要素の場合はn個の要素をスキャンし、2番目の要素はn-1個の要素をスキャンする必要があります。したがって、複雑さはn ^ 2のオーダーになります。私はより良い方法はソートしてから等号を使用することだと思います。それはO(n * log(n))の複雑さを持ちます
puneet

90

Apache Commons Collectionが再び助けになります:

List<String> listA = Arrays.asList("a", "b", "b", "c");
List<String> listB = Arrays.asList("b", "c", "a", "b");
System.out.println(CollectionUtils.isEqualCollection(listA, listB)); // true

 

List<String> listC = Arrays.asList("a", "b", "c");
List<String> listD = Arrays.asList("a", "b", "c", "c");
System.out.println(CollectionUtils.isEqualCollection(listC, listD)); // false

ドキュメント:

org.apache.commons.collections4.CollectionUtils

public static boolean isEqualCollection(java.util.Collection a,
                                        java.util.Collection b)

true指定さCollectionれたに、まったく同じ要素がまったく同じカーディナリティで含まれている場合は、を 返します。

すなわち、カーディナリティのときに限り、Eでの基数に等しいEにおけるB各要素のために、Eで又はB

パラメーター:

  • a -最初のコレクションは、 null
  • b -2番目のコレクションは、 null

戻り値: trueコレクションに同じカーディナリティの同じ要素が含まれている場合。


実装はDiddiZの回答とほぼ同じように見えます。
user227353 2015

わかりました...しかし、答えが正しければ、犯人(2つのリストに共通ではない要素)をつかむのはfalseどうでしょうか?私の答えを見てください。
マイクげっ歯類2016

implementation 'org.apache.commons:commons-collections4:4.3'エラーCaused by: com.android.builder.dexing.DexArchiveBuilderException: Failed to process パスを残しました。
Aliton Oliveira

13
// helper class, so we don't have to do a whole lot of autoboxing
private static class Count {
    public int count = 0;
}

public boolean haveSameElements(final List<String> list1, final List<String> list2) {
    // (list1, list1) is always true
    if (list1 == list2) return true;

    // If either list is null, or the lengths are not equal, they can't possibly match 
    if (list1 == null || list2 == null || list1.size() != list2.size())
        return false;

    // (switch the two checks above if (null, null) should return false)

    Map<String, Count> counts = new HashMap<>();

    // Count the items in list1
    for (String item : list1) {
        if (!counts.containsKey(item)) counts.put(item, new Count());
        counts.get(item).count += 1;
    }

    // Subtract the count of items in list2
    for (String item : list2) {
        // If the map doesn't contain the item here, then this item wasn't in list1
        if (!counts.containsKey(item)) return false;
        counts.get(item).count -= 1;
    }

    // If any count is nonzero at this point, then the two lists don't match
    for (Map.Entry<String, Count> entry : counts.entrySet()) {
        if (entry.getValue().count != 0) return false;
    }

    return true;
}

うわー、これが他のすべてのソリューションよりも高速に実行されていることに本当に驚いていました。そして、それはアーリーアウトをサポートします。
DiddiZ 2013

これは、count負になった場合に2番目のループでも短絡する可能性があり、ループ本体をif(!counts.containsKey(item) || --counts.get(item).count < 0) return false;次のように簡略化します。また、3番目のループを次のように簡略化できますfor(Count c: counts.values()) if(c.count != 0) return false;
Holger

@ホルガー私は最初のものに似ているものと考えました(ゼロに達したときにカウントを削除すると、同じ効果がありますが、最後のチェックも「counts空かどうかを返す」に変更します)、あいまいにしたくありませんでした要点:マップを使用すると、基本的にこれがO(N + M)問題に変わり、これが最も大きなブーストになります。
cHao

@cHaoはい、あなたは以前のものよりも時間の複雑さがより良いソリューションを指すためのクレジットに値する。イテラブルに関して最近同様の質問があるので、たまたまそれについて考えました。Java 8も使用できるようになったので、再考する価値がありました。数値が負になったときに2番目のループで短絡すると、3番目のループは廃止されます。さらに、ボクシングを回避することは両刃の剣である可能性があり、new Map.mergeでは、ボックス化された整数を使用する方がほとんどのユースケースでよりシンプルで効率的です。この回答も参照してください…
Holger

10

これらの答えはトリックを逃すと思います。

Blochは、彼の本質的で素晴らしい、簡潔なEffective Javaで、アイテム47のタイトル「ライブラリーを知って使用する」、「要約すると、車輪を再発明しない」と述べています。そして、そうでない理由をいくつか明らかにしている。

ここにはCollectionUtils、Apache Commons Collectionsライブラリのメソッドを提案するいくつかの回答がありますが、この質問に回答する最も美しくエレガントな方法を見つけたものはありません。

Collection<Object> culprits = CollectionUtils.disjunction( list1, list2 );
if( ! culprits.isEmpty() ){
  // ... do something with the culprits, i.e. elements which are not common

}

犯人:すなわち、両方に共通していない要素Lists。属する犯人を決定list1し、これにlist2使用して比較的簡単であるCollectionUtils.intersection( list1, culprits )CollectionUtils.intersection( list2, culprits )
ただし、{"a"、 "a"、 "b"} disjunctionと{"a"、 "b"、 "b"} ...のような場合は、ソフトウェアの障害ではなく、望ましいタスクの微妙さ/あいまいさの性質に固有です。

Apacheエンジニアが作成した、このようなタスクのソースコード(l。287)はいつでも調べることができます。それらのコードを使用する利点の1つは、多くのエッジケースと問題が予想され、対処された状態で、徹底的に試行およびテストされていることです。必要に応じて、このコードをコピーして心ゆくまで調整できます。


注意:最初にがっかりしたのCollectionUtilsは、どのメソッドにも独自のルールを課すことができるオーバーロードバージョンが用意されていないことですComparator(そのためequals、目的に合わせて再定義できます)。

しかし、collections4 4.0からは、Equator「T型のオブジェクト間の等価性を決定する」新しいクラスがあります。collections4 CollectionUtils.javaのソースコードを調べると、いくつかのメソッドでこれを使用しているようですが、私が知る限り、CardinalityHelperクラスを使用してファイルの上部にあるメソッドには適用できません... disjunctionおよびを含めますintersection

私はそれが非自明であるため、Apacheの人々は、まだこの程度持っていないと推測:あなたの代わりにその要素の固有の使用の「AbstractEquatingCollection」クラス、のようなものを作成しなければならないequalshashCodeメソッドを代わりにそれらを使用する必要がありますが以下のEquatorようなすべての基本的な方法についてaddcontainsなどNBはあなたがソースコードを見ると、実際には、AbstractCollection実装されていないadd、また、このようなとしての抽象サブクラスを行うAbstractSet...あなたのような具象クラスまで待たなければならないHashSetArrayList前にadd実装されています。かなり頭痛の種。

とりあえず、このスペースを見てください。明白な暫定的な解決策は、用途ラッパークラス特注内のすべての要素をラップするだろうequalsし、hashCodeそれから...あなたが望む平等の種類を実装するための操作Collectionsこれらのラッパーオブジェクトの。


また、賢明な誰かが「依存症のコストを知っている」と述べた
スタニスワフバランスキー

@StanislawBaranskiそれは興味深いコメントです。そのようなライブラリに依存しすぎてはいけないという提案はありますか?すでに大きな飛躍を遂げているコンピューターでOSを使用するとき、そうではありませんか?私がApacheライブラリを使用して嬉しいのは、実際にそれらを非常に高品質にして、それらのメソッドが「契約」に準拠し、徹底的にテストされていると想定しているためです。より信頼できる独自のコードを開発するのにどのくらいの時間がかかりますか?オープンソースのApacheライブラリからコードをコピーして精査することは、検討する必要があるかもしれません...
マイク齧歯類

8

アイテムのカーディナリティが重要でない場合(つまり、繰り返される要素は1つと見なされます)、ソートすることなくこれを行う方法があります。

boolean result = new HashSet<>(listA).equals(new HashSet<>(listB));

これSetにより、それぞれのが作成され、(もちろん)順序を無視するのメソッドがList使用されます。HashSetequals

カーディナリティが重要な場合は、によって提供される機能に制限する必要がありますList。その場合、@ jschoenの答えがより適切になります。


listA = [a、b、c、c]、およびlistB = [a、b、c]の場合はどうなりますか。結果はtrueになりますが、リストは等しくありません。
Nikolas

6

リストをグアバのマルチセットに変換することは非常にうまくいきます。それらは順序に関係なく比較され、重複する要素も考慮されます。

static <T> boolean equalsIgnoreOrder(List<T> a, List<T> b) {
    return ImmutableMultiset.copyOf(a).equals(ImmutableMultiset.copyOf(b));
}

assert equalsIgnoreOrder(ImmutableList.of(3, 1, 2), ImmutableList.of(2, 1, 3));
assert !equalsIgnoreOrder(ImmutableList.of(1), ImmutableList.of(1, 1));

6

これは@cHaoソリューションに基づいています。いくつかの修正とパフォーマンスの改善が含まれています。これは、equals-ordered-copyソリューションの約2倍の速度で実行されます。どのコレクションタイプでも機能します。空のコレクションとnullは等しいと見なされます。あなたの利点に使用してください;)

/**
 * Returns if both {@link Collection Collections} contains the same elements, in the same quantities, regardless of order and collection type.
 * <p>
 * Empty collections and {@code null} are regarded as equal.
 */
public static <T> boolean haveSameElements(Collection<T> col1, Collection<T> col2) {
    if (col1 == col2)
        return true;

    // If either list is null, return whether the other is empty
    if (col1 == null)
        return col2.isEmpty();
    if (col2 == null)
        return col1.isEmpty();

    // If lengths are not equal, they can't possibly match
    if (col1.size() != col2.size())
        return false;

    // Helper class, so we don't have to do a whole lot of autoboxing
    class Count
    {
        // Initialize as 1, as we would increment it anyway
        public int count = 1;
    }

    final Map<T, Count> counts = new HashMap<>();

    // Count the items in col1
    for (final T item : col1) {
        final Count count = counts.get(item);
        if (count != null)
            count.count++;
        else
            // If the map doesn't contain the item, put a new count
            counts.put(item, new Count());
    }

    // Subtract the count of items in col2
    for (final T item : col2) {
        final Count count = counts.get(item);
        // If the map doesn't contain the item, or the count is already reduced to 0, the lists are unequal 
        if (count == null || count.count == 0)
            return false;
        count.count--;
    }

    // At this point, both collections are equal.
    // Both have the same length, and for any counter to be unequal to zero, there would have to be an element in col2 which is not in col1, but this is checked in the second loop, as @holger pointed out.
    return true;
}

合計カウンタを使用して、最後のforループをスキップできます。合計カウンターは、各ステージでのカウントの合計をカウントします。最初のforループで合計カウンターを増やし、2番目のforループで減らします。合計カウンタが0より大きい場合、リストは一致しません。それ以外の場合は一致します。現在、最後のforループでは、すべてのカウントがゼロかどうか、つまりすべてのカウントの合計がゼロかどうかを確認します。合計カウンタの種類を使用すると、このチェックが逆になり、カウントの合計がゼロの場合はtrue、それ以外の場合はfalseが返されます。
SatA 2016年

IMO、リストが一致する場合(最悪のシナリオ)、forループが別の不要なO(n)を追加するため、そのforループをスキップする価値があります。
SatA 2016年

@SatAは実際には、3番目のループを置き換えることなく削除できます。2番目のループはfalse、キーが存在しない場合、またはそのカウントが負になった場合にすでに戻ります。両方のリストの合計サイズが一致しているため(これは事前に確認されています)、別のキーの負の値なしにキーの正の値は存在できないため、2番目のループの後にゼロ以外の値を持つことはできません。
Holger

@holgerそれはあなたが絶対に正しいようです。私の知る限り、3番目のループはまったく必要ありません。
SatA

@SatA…そしてJava 8では、これはこの回答のように簡潔に実装できます。
Holger

5

コンピューターやプログラミング言語がない場合、自分でこれをどのように行うかを考えてください。要素の2つのリストを提供しますが、同じ要素が含まれているかどうかを教えてください。どうしますか

上記の1つのアプローチは、リストをソートしてから、要素ごとに移動して、それらが等しいかどうかを確認することです(これは何をList.equals行うかです)。これは、リストを変更することを許可されているか、リストをコピーすることが許可されていることを意味します。割り当てを知らなければ、どちらかまたは両方が許可されているかどうかはわかりません。

別の方法としては、各リストを調べ、各要素が出現する回数をカウントします。両方のリストの最後に同じ数がある場合、それらは同じ要素を持っています。そのためのコードは、各リストをマップに変換することですelem -> (# of times the elem appears in the list)equalsし、2つのマップを呼び出すことです。マップがの場合、HashMapこれらの変換はそれぞれ、比較と同様にO(N)操作です。これにより、時間の面でかなり効率的なアルゴリズムが得られますが、追加のメモリが必要になります。


5

私はこれと同じ問題を抱えており、別の解決策を考え出しました。これは重複が含まれる場合にも機能します。

public static boolean equalsWithoutOrder(List<?> fst, List<?> snd){
  if(fst != null && snd != null){
    if(fst.size() == snd.size()){
      // create copied lists so the original list is not modified
      List<?> cfst = new ArrayList<Object>(fst);
      List<?> csnd = new ArrayList<Object>(snd);

      Iterator<?> ifst = cfst.iterator();
      boolean foundEqualObject;
      while( ifst.hasNext() ){
        Iterator<?> isnd = csnd.iterator();
        foundEqualObject = false;
        while( isnd.hasNext() ){
          if( ifst.next().equals(isnd.next()) ){
            ifst.remove();
            isnd.remove();
            foundEqualObject = true;
            break;
          }
        }

        if( !foundEqualObject ){
          // fail early
          break;
        }
      }
      if(cfst.isEmpty()){ //both temporary lists have the same size
        return true;
      }
    }
  }else if( fst == null && snd == null ){
    return true;
  }
  return false;
}

他のいくつかのソリューションと比較した利点:

  • O(N²)未満の複雑さ(ただし、ここでは他の回答のソリューションと比較して実際のパフォーマンスをテストしていません)。
  • 早く終了します。
  • nullをチェックします。
  • 重複が関係する場合でも機能します。配列[1,2,3,3]と別の配列[1,2,2,3]がある場合、ここでのほとんどの解決策は、順序を考慮しない場合は同じであることを通知します。このソリューションは、一時リストから等しい要素を削除することでこれを回避します。
  • セマンティックイコールを使用します(equals参照等価(==)ではなく、)をます。
  • itensは並べ替えられないimplement Comparableため、このソリューションが機能するために(によって)並べ替え可能である必要はありません。

3

コレクションをソートしたくない場合、["A" "B" "C"]が["B" "B" "A" "C"]と等しくないという結果が必要な場合は、

l1.containsAll(l2)&&l2.containsAll(l1)

十分ではありません、おそらくサイズもチェックする必要があります:

    List<String> l1 =Arrays.asList("A","A","B","C");
    List<String> l2 =Arrays.asList("A","B","C");
    List<String> l3 =Arrays.asList("A","B","C");

    System.out.println(l1.containsAll(l2)&&l2.containsAll(l1));//cautions, this will be true
    System.out.println(isListEqualsWithoutOrder(l1,l2));//false as expected

    System.out.println(l3.containsAll(l2)&&l2.containsAll(l3));//true as expected
    System.out.println(isListEqualsWithoutOrder(l2,l3));//true as expected


    public static boolean isListEqualsWithoutOrder(List<String> l1, List<String> l2) {
        return l1.size()==l2.size() && l1.containsAll(l2)&&l2.containsAll(l1);
}

2

CollectionUtils減算メソッドを活用するソリューション:

import static org.apache.commons.collections15.CollectionUtils.subtract;

public class CollectionUtils {
  static public <T> boolean equals(Collection<? extends T> a, Collection<? extends T> b) {
    if (a == null && b == null)
      return true;
    if (a == null || b == null || a.size() != b.size())
      return false;
    return subtract(a, b).size() == 0 && subtract(a, b).size() == 0;
  }
}

1

順序を気にする場合は、equalsメソッドを使用します。

list1.equals(list2)

あなたが注文を気にしない場合は、これを使用してください

Collections.sort(list1);
Collections.sort(list2);      
list1.equals(list2)

4
彼は秩序を気にしないと言います。
マイクげっ歯類2016

0

両方の長所[@ DiddiZ、@ Chalkos]:これは主に@Chalkosメソッドに基づいて構築されていますが、バグ(ifst.next())を修正し、初期チェック(@DiddiZから取得)を改善するだけでなく、最初のコレクションをコピーします(2番目のコレクションのコピーからアイテムを削除するだけです)。

ハッシュ関数やソートを必要とせず、不平等の早期存在を可能にする、これはまだ最も効率的な実装です。それは、数千以上のコレクションの長さと非常に単純なハッシュ関数がない限りです。

public static <T> boolean isCollectionMatch(Collection<T> one, Collection<T> two) {
    if (one == two)
        return true;

    // If either list is null, return whether the other is empty
    if (one == null)
        return two.isEmpty();
    if (two == null)
        return one.isEmpty();

    // If lengths are not equal, they can't possibly match
    if (one.size() != two.size())
        return false;

    // copy the second list, so it can be modified
    final List<T> ctwo = new ArrayList<>(two);

    for (T itm : one) {
        Iterator<T> it = ctwo.iterator();
        boolean gotEq = false;
        while (it.hasNext()) {
            if (itm.equals(it.next())) {
                it.remove();
                gotEq = true;
                break;
            }
        }
        if (!gotEq) return false;
    }
    // All elements in one were found in two, and they're the same size.
    return true;
}

私が間違っていない場合、価値のあるシナリオ(リストは等しいが逆の方法で並べ替えられる)でのこのアルゴリズムの複雑さはO(N * N!)になります。
SatA 2016年

実際、これはO(N *(N / 2))になります。反復ごとに、配列サイズが減少します。
jazzgil 2016

0

これは、null値を含む可能性のある配列リストが等しいかどうかを確認する別の方法です。

List listA = Arrays.asList(null, "b", "c");
List listB = Arrays.asList("b", "c", null);

System.out.println(checkEquality(listA, listB)); // will return TRUE


private List<String> getSortedArrayList(List<String> arrayList)
{
    String[] array = arrayList.toArray(new String[arrayList.size()]);

    Arrays.sort(array, new Comparator<String>()
    {
        @Override
        public int compare(String o1, String o2)
        {
            if (o1 == null && o2 == null)
            {
                return 0;
            }
            if (o1 == null)
            {
                return 1;
            }
            if (o2 == null)
            {
                return -1;
            }
            return o1.compareTo(o2);
        }
    });

    return new ArrayList(Arrays.asList(array));
}

private Boolean checkEquality(List<String> listA, List<String> listB)
{
    listA = getSortedArrayList(listA);
    listB = getSortedArrayList(listB);

    String[] arrayA = listA.toArray(new String[listA.size()]);
    String[] arrayB = listB.toArray(new String[listB.size()]);

    return Arrays.deepEquals(arrayA, arrayB);
}

リストと配列の間のこのすべてのコピーのポイントは何ですか?
Holger

0

これに対する私の解決策。それはそれほどクールではありませんが、うまくいきます。

public static boolean isEqualCollection(List<?> a, List<?> b) {

    if (a == null || b == null) {
        throw new NullPointerException("The list a and b must be not null.");
    }

    if (a.size() != b.size()) {
        return false;
    }

    List<?> bCopy = new ArrayList<Object>(b);

    for (int i = 0; i < a.size(); i++) {

        for (int j = 0; j < bCopy.size(); j++) {
            if (a.get(i).equals(bCopy.get(j))) {
                bCopy.remove(j);
                break;
            }
        }
    }

    return bCopy.isEmpty();
}

-1

その場合、リスト{"a"、 "b"}と{"b"、 "a"}は等しくなります。そして、{"a"、 "b"}と{"b"、 "a"、 "c"}は等しくありません。複雑なオブジェクトのリストを使用する場合、containsAllが内部で使用するため、equalsメソッドをオーバーライドすることを忘れないでください。

if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){
        areEqual = true;
}

-1:{"a"、 "a"、 "b"}および{"a"、 "b"、 "b"}で間違った答えを出します:のソースコードを確認してくださいAbstractCollection.containsAll()。についてListsではなく、について話しているときに、要素の重複を許可する必要がありSetsます。私の答えを見てください。
マイクげっ歯類
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.