マップとは異なる値を持つマップを作成する方法(およびBinaryOperatorを使用して正しいキーを使用する方法)


13

私はマップを持ってMap<K, V>おり、私の目標は重複した値を削除して、まったく同じ構造をMap<K, V>再び出力することです。重複値が見つかった場合には、一つのキー(が選択されなければならないk二つの鍵(から)k1k1、これらの値を保持する)、このため、想定BinaryOperator<K>を与えるkからk1k2入手可能です。

入力と出力の例:

// Input
Map<Integer, String> map = new HashMap<>();
map.put(1, "apple");
map.put(5, "apple");
map.put(4, "orange");
map.put(3, "apple");
map.put(2, "orange");

// Output: {5=apple, 4=orange} // the key is the largest possible

使用して私の試みはStream::collect(Supplier, BiConsumer, BiConsumer)あるビット非常に不器用とのような変更可能な操作が含まMap::putMap::remove私は避けたいたの。

// // the key is the largest integer possible (following the example above)
final BinaryOperator<K> reducingKeysBinaryOperator = (k1, k2) -> k1 > k2 ? k1 : k2;

Map<K, V> distinctValuesMap = map.entrySet().stream().collect(
    HashMap::new,                                                              // A new map to return (supplier)
    (map, entry) -> {                                                          // Accumulator
        final K key = entry.getKey();
        final V value = entry.getValue();
        final Entry<K, V> editedEntry = Optional.of(map)                       // New edited Value
            .filter(HashMap::isEmpty)
            .map(m -> new SimpleEntry<>(key, value))                           // If a first entry, use it
            .orElseGet(() -> map.entrySet()                                    // otherwise check for a duplicate
                    .stream() 
                    .filter(e -> value.equals(e.getValue()))
                    .findFirst()
                    .map(e -> new SimpleEntry<>(                               // .. if found, replace
                            reducingKeysBinaryOperator.apply(e.getKey(), key), 
                            map.remove(e.getKey())))
                    .orElse(new SimpleEntry<>(key, value)));                   // .. or else leave
        map.put(editedEntry.getKey(), editedEntry.getValue());                 // put it to the map
    },
    (m1, m2) -> {}                                                             // Combiner
);

Collectors1つのStream::collect呼び出し内で適切な組み合わせを使用する解決策はありますか(たとえば、変更可能な操作なし)?


2
より良い」または「最高」の指標は何ですか?StreamS を介して実行する必要がありますか?
Turing85

同じ値が2つのキーに関連付けられている場合、どのキーを保持するかをどのように選択しますか?
マイケル

あなたの場合に期待される出力は何ですか?
YCF_L

1
@ Turing85:私が言ったように。より良いか、最高のような変更可能なマップ・メソッドの明示的な使用となりMap::putまたはMap::removeCollector
ニコラス

1
を見てみる価値がありますBiMap。おそらく、JavaのHashMapから重複する値
Naman

回答:


12

Collectors.toMapを使用できます

private Map<Integer, String> deduplicateValues(Map<Integer, String> map) {
    Map<String, Integer> inverse = map.entrySet().stream().collect(toMap(
            Map.Entry::getValue,
            Map.Entry::getKey,
            Math::max) // take the highest key on duplicate values
    );

    return inverse.entrySet().stream().collect(toMap(Map.Entry::getValue, Map.Entry::getKey));
}

9

これを試してください:単純な方法は、キーと値を逆にしてから、toMap()コレクターとマージ機能を使用することです。

map.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleEntry<>(entry.getValue(), entry.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, reducingKeysBinaryOperator));

Map<K, V> output = map.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, reducingKeysBinaryOperator))
        .entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));

2
中間map操作が何を購入するのかわかりません。あなたはキーと値を交換しているようですが、それは明らかですが、要点は、収集ステップでも同じようにできるということですか?
GPI

3
@GPIとマイケル、これは彼がキーをマージする必要があるため、ペアを反転するとキーがマージされます。不足しているのは、2番目の反転です。
ジャンバティストユネス

2
@HadiJいいえ!反転は正しかった!しかし、2つ目は戻るために必要でした。マージはキーのマージに使用されますが、マージは値に対してのみ可能です...
Jean-BaptisteYunèsJan

@Jean-BaptisteYunèsマージの必要性は理解していますが、すぐに取得できないのは、のswap(); collect(key, value, binOp);代わりにコーディングする理由ですcollect(value, key, binOp)。多分私はこれをjshellで実際に試す必要がありますか?
GPI

2
あなたが共有したコードで、質問で紹介されたローカル変数を自由に使用できるようにしました。答えを出している間に意図と矛盾する場合は元に戻してください。
ナマン

4

非ストリームソリューションの方が表現力があると思います。

BinaryOperator<K> reducingKeysBinaryOperator = (k1, k2) -> k1 > k2 ? k1 : k2;

Map<V, K> reverse = new LinkedHashMap<>(map.size());
map.forEach((k, v) -> reverse.merge(v, k, reducingKeysBinaryOperator));

Map<K, V> result = new LinkedHashMap<>(reverse.size());
reverse.forEach((v, k) -> result.put(k, v));

これはMap.merge、縮小二機能で使用LinkedHashMapし、元のエントリの順序を維持するために使用します。


2
はい、私はこの(同様の)解決策を結論付けました。ただし、より宣言的な方法であるため、私はjava-streamアプローチを探しています。+1してください
ニコラス

1

Collectors返されたMapを再度収集してさらに処理する必要がない場合のみ使用する方法を見つけました。アイデアは:

  1. をにグループ化Map<K, V>Map<V, List<K>ます。

    Map<K, V> distinctValuesMap = this.stream.collect(
        Collectors.collectingAndThen(
            Collectors.groupingBy(Entry::getValue),
            groupingDownstream 
        )
    );

    {apple = [1、5、3]、orange = [4、2]}

  2. 新しいキー(リデュースList<K>)にK使用しますBinaryOperator<K>

    Function<Entry<V, List<Entry<K, V>>>, K> keyMapFunction = e -> e.getValue().stream()
        .map(Entry::getKey)
        .collect(Collectors.collectingAndThen(
            Collectors.reducing(reducingKeysBinaryOperator),
            Optional::get
        )
    );

    {apple = 5、orange = 4}

  3. キーと値の両方が別個であることが保証されているため、構造にMap<V, K>戻る逆をMap<K, V>再び逆にします。

    Function<Map<V, List<Entry<K,V>>>, Map<K, V>> groupingDownstream = m -> m.entrySet()
        .stream()
        .collect(Collectors.toMap(
            keyMapFunction,
            Entry::getKey
        )
    );

    {5 =りんご、4 =オレンジ}

最終的なコード:

final BinaryOperator<K> reducingKeysBinaryOperator = ...

final Map<K, V> distinctValuesMap = map.entrySet().stream().collect(
        Collectors.collectingAndThen(
            Collectors.groupingBy(Entry::getValue),
            m -> m.entrySet().stream().collect(
                Collectors.toMap(
                    e -> e.getValue().stream().map(Entry::getKey).collect(
                        Collectors.collectingAndThen(
                            Collectors.reducing(reducingKeysBinaryOperator),
                            Optional::get
                        )
                    ),
                    Entry::getKey
                )
            )
        )
    );

1

「Stream and Collectors.groupingBy」で望ましい結果を得る別の方法。

    map = map.entrySet().stream()
    .collect(Collectors.groupingBy(
            Entry::getValue,
            Collectors.maxBy(Comparator.comparing(Entry::getKey))
            )
    )
    .entrySet().stream()
    .collect(Collectors.toMap(
            k -> {
                return k.getValue().get().getKey();
            }, 
            Entry::getKey));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.