1. 概要

Set は、Javaで一般的に使用されるコレクションタイプの1つです。 今日は、与えられた2つのセットの違いを見つける方法について説明します。

2. 問題の紹介

実装を詳しく見る前に、まず問題を理解する必要があります。 いつものように、例は要件をすばやく理解するのに役立つ場合があります。

2つのSetオブジェクトset1set2があるとします。

set1: {"Kotlin", "Java", "Rust", "Python", "C++"}
set2: {"Kotlin", "Java", "Rust", "Ruby", "C#"}

ご覧のとおり、両方のセットにいくつかのプログラミング言語名が含まれています。 「2つのセットの違いを見つける」という要件には、次の2つのバリエーションがあります。

  • 非対称の違い– set1 に含まれているが、set2には含まれていない要素を検索します。 この場合、期待される結果は {“ Python”、“ C ++”}です。
  • 対称差–いずれかのセットの要素を検索しますが、それらの共通部分は検索しません。 この例を見ると、結果は {“ Python”、“ C ++”、“ Ruby”、“ C#”}になります。

このチュートリアルでは、両方のシナリオの解決策について説明します。 まず、非対称の違いを見つけることに焦点を当てます。 その後、2つのセット間の対称差を見つける方法を検討します。

次に、それらの動作を見てみましょう。

3. 非対称の違い

3.1. 標準のremoveAllメソッドを使用する

Set クラスは、removeAllメソッドを提供しました。 このメソッドは、CollectionインターフェースからremoveAllメソッドを実装します。

removeAllメソッドは、Collectionオブジェクトをパラメーターとして受け入れ、指定されたSetオブジェクトからパラメーター内のすべての要素を削除します。したがって、この方法で set2 オブジェクトをパラメーターとして渡すと、 「set1.removeAll(set2)」、set1オブジェクトの残りの要素が結果になります。

簡単にするために、単体テストとして表示しましょう。

Set<String> set1 = Stream.of("Kotlin", "Java", "Rust", "Python", "C++").collect(Collectors.toSet());
Set<String> set2 = Stream.of("Kotlin", "Java", "Rust", "Ruby", "C#").collect(Collectors.toSet());
Set<String> expectedOnlyInSet1 = Set.of("Python", "C++");

set1.removeAll(set2);

assertThat(set1).isEqualTo(expectedOnlyInSet1);

上記の方法が示すように、最初に、Streamを使用して2つのSetオブジェクトを初期化します。 次に、 removeAll メソッドを呼び出した後、 set1オブジェクトに予期される要素が含まれます。

このアプローチは非常に簡単です。 ただし、欠点は明らかです。 set1 から共通要素を削除した後、元のset1が変更されます

したがって、 removeAllメソッドを呼び出した後も元のset1オブジェクトが必要な場合はバックアップする必要があり、set1が不変のSetの場合は新しい可変セットオブジェクトを作成する必要があります。 ]

次に、元のセットを変更せずに、新しいSetオブジェクトで非対称の違いを返す別のアプローチを見てみましょう。

3.2. Stream.filterメソッドの使用

StreamAPIはJava8から登場しています。 Stream.filter メソッドを使用して、コレクションから要素をフィルタリングできます。

元のset1オブジェクトを変更せずに、Stream.filterを使用してこの問題を解決することもできます。 まず、2つのセットを不変のセットとして初期化します。

Set<String> immutableSet1 = Set.of("Kotlin", "Java", "Rust", "Python", "C++");
Set<String> immutableSet2 = Set.of("Kotlin", "Java", "Rust", "Ruby", "C#");
Set<String> expectedOnlyInSet1 = Set.of("Python", "C++");

Java 9以降、Setインターフェースは静的なofメソッドを導入しました。 これにより、不変のSetオブジェクトを便利に初期化できます。 つまり、 immutableSet1、を変更しようとすると、UnsupportedOperationExceptionがスローされます。

次に、Stream.filterを使用して違いを見つける単体テストを作成しましょう。

Set<String> actualOnlyInSet1 = immutableSet1.stream().filter(e -> !immutableSet2.contains(e)).collect(Collectors.toSet());
assertThat(actualOnlyInSet1).isEqualTo(expectedOnlyInSet1);

上記の方法でわかるように、キーは「 filter(e->!immutableSet2.contains(e))」です。 ここでは、 immutableSet1 にある要素のみを取得し、immutableSet2には含まれていません。

このテストメソッドを実行すると、例外なく合格します。 これは、このアプローチが機能し、元のセットが変更されていないことを意味します。

3.3. グアバライブラリの使用

Guava は、いくつかの新しいコレクションタイプと便利なヘルパーメソッドが付属している人気のあるJavaライブラリです。 Guavaは、2つのセット間の非対称の違いを見つける方法を提供しています。 したがって、この方法を使用して問題を簡単に解決できます。

ただし、最初に、ライブラリをクラスパスに含める必要があります。 プロジェクトの依存関係をMavenで管理するとします。 Guava依存関係pom.xmlに追加する必要がある場合があります。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

JavaプロジェクトでGuavaが利用可能になると、そのSets.differenceメソッドを使用して、期待される結果を得ることができます

Set<String> actualOnlyInSet1 = Sets.difference(immutableSet1, immutableSet2);
assertThat(actualOnlyInSet1).isEqualTo(expectedOnlyInSet1);

Sets.differenceメソッドは、結果を含む不変のSetビューを返すことに注意してください。 その意味は:

  • 返されたセットを変更することはできません
  • 元のセットが変更可能なセットである場合、元のセットへの変更が結果のセットビューに反映される場合があります

3.4. ApacheCommonsライブラリの使用

Apache Commonsは、広く使用されているもう1つのライブラリです。 Apache Commons Collections4 ライブラリは、標準のCollection APIを補完するものとして、多くの優れたコレクション関連のメソッドを提供します。

使用を開始する前に、pom.xmlに依存関係を追加しましょう。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

同様に、最新バージョンはMavenの中央リポジトリにあります。

commons-collections4ライブラリにはCollectionUtils.removeAllメソッドがあります。 これは標準のCollection.removeAllメソッドに似ていますが、は、最初のCollectionオブジェクトを変更する代わりに、新しいCollectionオブジェクトで結果を返します。

次に、2つの不変のSetオブジェクトでテストしてみましょう。

Set<String> actualOnlyInSet1 = new HashSet<>(CollectionUtils.removeAll(immutableSet1, immutableSet2));
assertThat(actualOnlyInSet1).isEqualTo(expectedOnlyInSet1);

テストを実行すると合格します。 ただし、CollectionUtils.removeAllメソッドはコレクションタイプの結果を返すことに注意してください。

具体的なタイプが必要な場合(たとえば、この場合は Set )、手動で変換する必要があります。 上記のテストメソッドでは、返されたコレクションを使用して、新しいHashSetオブジェクトを初期化しました。

4. 対称差

これまで、2つのセット間の非対称の違いを取得する方法を学びました。 次に、他のシナリオを詳しく見てみましょう。2つのセット間の対称差を見つけることです。

2つの不変セットの例から対称差を取得するための2つのアプローチについて説明します。

期待される結果は次のとおりです。

Set<String> expectedDiff = Set.of("Python", "C++", "Ruby", "C#");

次に、問題を解決する方法を見てみましょう。

4.1. HashMapを使用する

問題を解決するための1つのアイデアは、最初に作成することです。 地図物体。

次に、指定された2つのセットを繰り返し処理し、各要素をキーとしてマップに配置します。 キーがマップに存在する場合、これは両方のセットに共通の要素であることを意味します。 値として特別な数値を設定します(例:Integer.MAX_VALUE) 。 それ以外の場合は、要素と値1をマップの新しいエントリとして配置します。

最後に、マップ内で値が1であるキーを見つけます。これらのキーは、指定された2つのセット間の対称差です。

次に、Javaでアイデアを実装しましょう。

public static <T> Set<T> findSymmetricDiff(Set<T> set1, Set<T> set2) {
    Map<T, Integer> map = new HashMap<>();
    set1.forEach(e -> putKey(map, e));
    set2.forEach(e -> putKey(map, e));
    return map.entrySet().stream()
      .filter(e -> e.getValue() == 1)
      .map(Map.Entry::getKey)
      .collect(Collectors.toSet());
}

private static <T> void putKey(Map<T, Integer> map, T key) {
    if (map.containsKey(key)) {
        map.replace(key, Integer.MAX_VALUE);
    } else {
        map.put(key, 1);
    }
}

それでは、ソリューションをテストして、期待どおりの結果が得られるかどうかを確認しましょう。

Set<String> actualDiff = SetDiff.findSymmetricDiff(immutableSet1, immutableSet2);
assertThat(actualDiff).isEqualTo(expectedDiff);

テストを実行すると合格します。 つまり、実装は期待どおりに機能します。

4.2. ApacheCommonsライブラリの使用

2つのセット間の非対称の違いを見つけるときに、ApacheCommonsライブラリをすでに紹介しました。 実際、 commons-collections4ライブラリには、2つのセット間の対称差を直接返すための便利なSetUtils.disjunctionメソッドがあります

Set<String> actualDiff = SetUtils.disjunction(immutableSet1, immutableSet2);
assertThat(actualDiff).isEqualTo(expectedDiff);

上記のメソッドが示すように、 CollectionUtils.removeAll メソッドとは異なり、SetUtils.disjunctionメソッドはSetオブジェクトを返します。 手動でSetに変換する必要はありません。

5. 結論

この記事では、例を通して2つのSetオブジェクトの違いを見つける方法を探りました。 さらに、この問題の2つの変形について説明しました。非対称の違いと対称の違いを見つけることです。

標準のJavaAPIと、ApacheCommons-CollectionsやGuavaなどの広く使用されている外部ライブラリを使用して2つのバリアントを解決する方法について説明しました。

いつものように、このチュートリアルで使用されているソースコードは、GitHubからで入手できます。