2つの異なるリストにまったく同じ要素が含まれているかどうかを確認する簡単な方法は?


253

標準Javaライブラリで、2つのリストにまったく同じ要素が含まれているかどうかを確認する最も簡単な方法は何ですか?

2つのリストが同じインスタンスであるかどうかは関係ありません。また、リストのtypeパラメータが異なるかどうかは関係ありません。

例えば

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

おそらく私が知っている顔に私を見つめている何かがあるでしょう:-)


編集:明確にするために、正確に同じ要素と要素の数を順番に探していました。


要素は同じ順序である必要がありますか?
マイケルマイヤーズ

これは、契約等しくなる永続的なセットは時々尊重していない休止あなたに影響を与えませんが、注意しないかもしれない-検索を参照してくださいopensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim

回答:


367

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

list1.equals(list2)

javadocから:

指定されたオブジェクトがこのリストと等しいかどうかを比較します。指定されたオブジェクトもリストであり、両方のリストのサイズが同じで、2つのリスト内の対応する要素のペアがすべて等しい場合にのみ、trueを返します。((e1 == null?e2 == null:e1.equals(e2))の場合、2つの要素e1とe2は等しくなります。つまり、同じ要素が同じ順序で含まれている場合、2つのリストは等しいと定義されます。 。この定義により、equalsメソッドがListインターフェースのさまざまな実装にわたって適切に機能することが保証されます。

順序に関係なくチェックしたい場合は、すべての要素をセットにコピーし、結果のセットで等号を使用できます。

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

このアプローチの制限は、順序だけでなく重複要素の頻度も無視することです。たとえば、list1["A"、 "B"、 "A"]であり、list2["A"、 "B"、 "B"]の場合、Setアプローチはそれらが等しいと見なします。

順序に敏感ではないが重複の頻度に敏感である必要がある場合は、次のいずれかを実行できます。


54
注文とは別にチェックしたい場合は、containsAllを使用できませんか?
2009

6
containsAllの実装の詳細についてはわかりませんが、それは悪いことのようです。containsAllがcontains()を何度も呼び出すと、O(n ^ 2)アルゴリズムが作成されます。セット全体はO(nlogn)である必要があります
Tom

6
実際、セットがO(nlogn)になる場合、別のアプローチはリストでCollections.sort()を呼び出してから、equalsを使用することです。ただし、順序を維持したい場合は、リストをコピーする必要があります。これはコストが高く、セットソリューションを優先する可能性があります。そのため、状況について考える必要があります:-)。
トム

1
@amischiefr:O(n ^ 2)があなたにできる最善の方法を示唆していますか?
トム

8
@Dennis実際にサイズチェックが機能するのは、各リストに個別の要素しか含まれていない場合のみです。たとえば、指定さa = [x, y, x]れたb = [x, y, z]場合、サイズは等しく、b.containsAll(a)trueを返しますが、にbはない要素が含まれていますa
ローレンスゴンサルベス2014

95

私はコメントでたくさんのものを投稿しました。

誰もがここで言うように、equals()の使用は順序に依存します。順序を気にしない場合は、3つのオプションがあります。

オプション1

を使用しcontainsAll()ます。このオプションは、最悪の場合のパフォーマンスO(n ^ 2)を提供するため、私の意見では理想的ではありません。

オプション2

これには2つのバリエーションがあります。

2a)リストの順序を維持する必要がない場合はCollections.sort()、両方のリストで使用します。次にを使用しequals()ます。これはO(nlogn)です。これは、2つのソートを実行してからO(n)の比較を行うためです。

2b)リストの順序を維持する必要がある場合は、最初に両方のリストをコピーできます。次に、ソリューション2aを使用できます次に、コピーした両方のリストでをます。ただし、コピーが非常に高価な場合、これは魅力的でない場合があります。

これはにつながります:

オプション3

要件がパート2bと同じであるが、コピーが高すぎる場合。TreeSetを使用してソートを実行できます。各リストを独自のTreeSetにダンプします。セットでソートされ、元のリストはそのまま残ります。次にequals()、両方ので比較を実行しますTreeSetTreeSetsSはO(nlogn)時間で構築することができ、equals() O(N)です。

好きなのを選びな :-)。

編集:ローレンス・ゴンサルベスが指摘したのと同じ警告をほとんど忘れていました。TreeSet実装は重複を排除します。重複が気になる場合は、ソートされたマルチセットのようなものが必要になります。


重複を気にする場合は、他のテストの前に、コレクションのサイズが等しいことを常にテストできます。
2009

より具体的には、重複があることが不平等を示す場合、等式チェックが成功する前に、リストのサイズは同じでなければなりません。
2009

7
@laz:2つのリストで異なる要素が重複している場合、サイズのチェックは機能しません。例:[A、A、B]と[A、B、B]は同じサイズです。
ローレンスゴンサルベス

@Laurence:lazの投稿は少しわかりにくいことに同意します(理解する前に何度か読みました)。彼は、(1)重複が問題であり、(2)リストのサイズが異なるという2つの条件が満たされた場合に、特別な場合に「ショートカット」を提供しようとしているだけだと思います。あなたの例では、私たちが議論したのと同じチェックをすべて行う必要があると、lazはまだ言っていると思います。(少なくともそれは私がそれを読む方法です)。重複が問題にならない場合は、サイズを特別なケースのチェックとして使用することはできません。2つの条件が成立する場合でも、あなただけの場合(list1.size()= list2.size()!)」と言うことができリターン偽;.
トム・

9
ContainsAllは間違った答えを出すと思いますが、containsAllを両方の方法で使用する必要があります。a.containsAll(b) && b.containsAll(a)
Richard Tingle 2017

24

Apache Commonsコレクションを使用している(または使用したい)場合は、CollectionUtils.isEqualCollectionを使用できます。これは、「特定のコレクションにまったく同じ要素がまったく同じカーディナリティで含まれている場合にtrueを返します」。


非常に素晴らしいハッシュマップベースの実装。ランタイムはO(n)である必要があり、繰り返し要素が多数ある場合は、最小限のメモリを使用して追跡します(基本的に、各コレクションのマップを使用して要素の頻度(カーディナリティ)を追跡します)。欠点は、追加のO(n)メモリ使用量があることです。
Muhd 2017

17

パーティーに非常に遅れましたが、このヌルセーフチェックを追加したいと思いました:

Objects.equals(list1, list2)

8

私はこれが古いスレッドであることを知っていますが、他のどの回答も私のユースケースを完全に解決しませんでした(Guava Multisetでも同じことができると思いますが、ここには例はありません)。申し訳ありませんがフォーマットしてください。私はまだスタック交換に投稿するのは初めてです。さらに、エラーがある場合はお知らせください

あなたが持ってList<T>いるとしましょうList<T> Bを、あなたは、彼らが次の条件と同等であるかどうかを確認したいです:

1)O(n)予想実行時間
2)等式は次のように定義されます:aまたはbのすべての要素について、aでの要素の出現回数は、bでの要素の出現回数と同じです。要素の等価性はT.equals()として定義されます

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
    if(a==null) {
        if(b==null) {
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
        } else {
            return false;
        }
    }
    if(b==null) {
        return false;
    }
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            tempMap.put(element, 1);
        } else {
            tempMap.put(element, currentCount+1);
        }
    }
    for(Object element : b) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            return false;
        } else {
            tempMap.put(element, currentCount-1);
        }
    }
    for(Integer count : tempMap.values()) {
        if(count != 0) {
            return false;
        }
    }
    return true;
}

ハッシュマップへのO(2 * n)挿入とO(3 * n)ハッシュマップ選択を実行しているため、実行時間はO(n)です。私はこのコードを完全にテストしていないので、注意してください:)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);

5

同じである必要はないが、同じ値を複数持つことをサポートするこのバージョンを試してください。それぞれが同じ量の値を持つ場合にのみ一致します。

public boolean arraysMatch(List<String> elements1, List<String> elements2) {
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) {
        return false;
    }
    List<String> work = newArrayList(elements2);
    for (String element : elements1) {
        if (!work.remove(element)) {
            return false;
        }
    }
    return work.isEmpty();
}

work.remove(element)はO(n)なので、このソリューションはO(n ^ 2)です
Andrew

または同じようなO(n1 * n2)
Lee Meador

すべてのシナリオを処理し、コレクションのサイズがそれほど大きくないため、同じ戦略を使用しました
。O

3

リストのequalsメソッドがこれを行います。リストは順序付けされているため、2つのリストが同じになるには、同じ要素が同じ順序である必要があります。

return list1.equals(list2);

3
リストは並べ替えない限り順序付けされません。
マイケルマイヤーズ

Sigh @ Myself。そのような明白な答え。Ctrl + FでもWebページを表示できなくなる日が長すぎることはご存じでしょう。:)
Grundlefleck 2009

2
@mmyers:リスト内のアイテムは、並べ替えない限り順序付けされません。リスト自体には、項目の暗黙的な順序付け(インデックスによる)があり、リスト内の項目を変更しない限り変更されません。(vs.セットまたはコレクションと、2回繰り返しても一貫した順序が保証されない場合)
Jason S

リストが順序付けられていると言うことでdavebが意味することは、List.equalsが要素の順序を考慮して等しいかどうかを判断することだと思います。Javadocを参照してください。
2009

2
つまり、{"A"、 "B"}を含むリストと{"B"、 "A"}を含むリストは、このメソッドとは等しくありません。それは意図したとおりのことかもしれませんが、誰もそれを見落とさないようにしたかったのです。
マイケルマイヤーズ

3

2つのリストの要素が同じで順序が異なる場合の解決策:

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
    if(isNullLists(listOne, listTwo)) {
        return false;
    }

    if (hasDifferentSize(listOne, listTwo)) {
        return true;
    }

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);
}

private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
    return listOne == null && listTwo == null;
}

private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
}

2
listTwoをコピーする必要はないと思います。
AjahnCharles 2017

1
また、removeAll()代わりに使用した理由に注意することもできますcontainsAll()(私の理解では、listTwoにlistOneに一度だけ含まれている重複が含まれている場合、containsAll()アプローチはリストを等しいと誤って報告します)。
AjahnCharles 2017

3

トムの答えは素晴らしいです私は彼の答えに完全に同意します!

この質問の興味深い側面は、List型自体とその固有の順序が必要かどうかです。

そうでない場合は、低下させることができるIterableか、またはCollection確認したいときではなく、挿入時にソートされたデータ構造を通過する際にある程度の柔軟性が得られます。

順序が重要でない場合(および要素が重複していない場合)、の使用を検討してくださいSet

順序が重要であるが挿入時間によって定義されている場合(および重複がない場合)LinkedHashSet、TreeSetのようなものであるが、挿入時間によって順序付けられている(重複はカウントされない)と考えてください。これにより、にO(1)組み込まれた償却済みアクセスも提供されますO(log n)


2

サンプルコード:

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) {

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) {
        sizePrevoisList = previousList.size();
    }
    if (newList != null && !newList.isEmpty()) {
        sizeNewList = newList.size();
    }

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
        return false;
    }

    if (sizeNewList != sizePrevoisList) {
        return true;
    }

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try {
        Collections.sort(n_prevois);
        Collections.sort(n_new);
    } catch (ClassCastException exp) {
        return true;
    }

    for (int i = 0; i < sizeNewList; i++) {
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) {
            // Object are same
        } else {
            return true;
        }
    }

    return false;
}

2

ローレンスの答えに加えて、ヌルセーフにしたい場合:

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

1
あなたはチェックを簡素化することができます:if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus

リストが[a、a、b、c]&[a、b、c]の場合は機能せず、リストのサイズが同じであることを確認する追加のチェックを追加しない限り、trueを返します。
Venkat Madhav

2
list1.equals(list2);

リストにカスタムクラスMyClassが含まれている場合、このクラスはequals関数をオーバーライドする必要があります。

 class MyClass
  {
  int field=0;
  @0verride
  public boolean equals(Object other)
        {
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        }
  }

注:ではなくjava.util.Setで同等をテストする場合は、java.util.ListオブジェクトでhashCode 関数をオーバーライドする必要があります。


1
必要な行:return this.field == MyClass.class.cast(other); this.field == MyClass.class.cast(other).field;を返す
alpere

@alpereああ!あなたが正しい !直します。よろしくお願いします!
ピエール


0

Apacheのorg.apache.commons.collectionsライブラリを使用できます。http//commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

これには、リスト要素が同じ順序である必要もあります。
josh-cain

比較する前にリストを並べ替えることができます
David Zhao

確かに、リストに格納されているタイプまたはソート可能なタイプ(またはコンパレーターがセットアップされているタイプ)であれば、それを行うことができます。ただし、Apache実装アルゴリズムは、静的であることを除いて、通常のlist1.equals(list2)と同じです。私が質問をどこで誤解していたのかわかります。実際、同じ順序でリストアイテムを比較する方法を尋ねていました。悪い!
josh-cain

@DavidZhao:リンクが切れています。
Aniket Kulkarni


0

両方のリストがnullでないことを確認してください。サイズが異なる場合、これらのリストは等しくありません。キーとしてのリストの要素と値としてのリピートで構成されるマップを作成し、マップを比較します。

仮定、両方のリストがnullの場合、それらは等しいと見なします。

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

注意してください、これらのオブジェクトに対して等しいメソッドが適切に定義されている必要があります。 https://stackoverflow.com/a/24814634/4587961


1
要素が各リストに異なる回数存在することはできないと想定しています。たとえば、[x, x, y]vs [x, y, y]は実装でtrueを返します。
AjahnCharles 2017

@CodeConfident、ありがとうございました!答えを更新しました。まおを使います!
Yan Khonski、2017

-2

使用している具象リストクラスによって異なります。抽象クラスAbstractCollectionには、別のコレクションを取得するcontainsAll(Collection)というメソッドがあります(リストはコレクションです)。

このコレクションに、指定されたコレクションのすべての要素が含まれている場合にtrueを返します。

したがって、ArrayListが渡されている場合は、このメソッドを呼び出して、それらがまったく同じかどうかを確認できます。

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

containsAll()の理由は、最初のリストを繰り返し処理して、2番目のリストで一致するものを探すためです。したがって、それらが順不同である場合、equals()はそれを取得しません。

編集:私はここで、提供されているさまざまなオプションを実行するための償却実行時間についてコメントしたいと思います。実行時間は重要ですか?承知しました。あなたが考慮すべき唯一のことですか?番号。

すべての単一の要素をリストから他のリストにコピーするコストは時間がかかり、かなりの量のメモリを消費します(使用しているメモリを事実上2倍にします)。

したがって、JVMのメモリが問題にならない場合(通常は問題になるはずです)、2つのリストから2つのTreeSetにすべての要素をコピーするのにかかる時間を考慮する必要があります。要素に入るときにすべての要素をソートすることを忘れないでください。

最後のアドバイスは?ここで適切な決定を下す前に、データセットとデータセット内の要素の数、およびデータセット内の各オブジェクトの大きさを考慮する必要があります。それらをいじって、片道ずつ作成し、どれがより速く実行されるかを確認してください。それは良い運動です。


2
foo.containsAll(bar)&& bar.containsAll(foo);である必要はありませんか。?
カールマナスター2009

いいえ、それはfooのすべての要素を調べ、barにその要素が含まれているかどうかを確認します。次に、2つのリストの長さが同じになるようにします。すべてのfooについて、foo.element == bar.elementおよびfoo.length == bar.lengthのような要素がbarにある場合、それらには同じ要素が含まれます。
amischiefr 2009

効率保証があるかどうかはわかりますか?または、これは通常O(n ^ 2)ですか?
トム

一致する要素を探して反復する他の配列と同様に、最悪の場合の実行時間はO(n ^ 2)になります。この場合、実装は実際に一度に1つの要素を反復して一致を探しているように見えます。償却実行時間については推測しませんが、はい、最悪のケースはO(n ^ 2)です。
amischiefr 2009

1
これは機能しません。{1,2,2} .containsAll({1,1,2})とその逆で、2つのリストは同じサイズです。
comco 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.