Як створити карту з різними значеннями з карти (і використовувати праву клавішу за допомогою BinaryOperator)?


13

У мене є карта, Map<K, V>і моя мета - видалити повторювані значення і знову вивести ту саму структуру Map<K, V>. У разі дублюється значення знайдено, то необхідно вибрати один ключ ( k) з двох клавіш ( k1і k1) , які тримають ці цінності, з цієї причини, припустимо , що BinaryOperator<K>дає kвід k1і k2доступно.

Приклад введення та виведення:

// 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::putі Map::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
);

Чи є рішення, використовуючи відповідну комбінацію в Collectorsмежах одного Stream::collectдзвінка (наприклад, без змінних операцій)?


2
Які ваші показники для " кращого " чи " найкращого "? Треба зробити це через Streams?
Turing85

Якщо одне і те ж значення пов’язане з 2 клавішами, як вибрати, який ключ зберігати?
Майкл

Які очікувані результати у вашому випадку?
YCF_L

1
@ Turing85: Як я вже сказав. Краще або краще було б, без явного використання змінюваних методів , таких як карти Map::putабо в Map::removeмежах Collector.
Ніколас

1
Варто поглянути BiMap. Можливо, дублікат Видалити повторювані значення з HashMap на Яві
Наман

Відповіді:


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
@HadiJ Ні! Інверсія була правильною! але другий був необхідний, щоб повернутися. Злиття використовується для об'єднання ключів, але злиття можливе лише для значень ...
Jean-Baptiste Yunès

@ 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без необхідності збирання та подальшої обробки повернутої карти знову. Ідея така:

  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], помаранчевий = [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
        )
    );

    {яблуко = 5, помаранчевий = 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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.