Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to produce map with distinct values from a map (and use the right key using BinaryOperator)?

I have a map Map<K, V> and my goal is to remove the duplicated values and output the very same structure Map<K, V> again. In case the duplicated value is found, there must be selected one key (k) from the two keys (k1 and k2) which hold these values, for this reason, assume the BinaryOperator<K> giving k from k1 and k2 is available.

Example input and output:

// 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

My attempt using Stream::collect(Supplier, BiConsumer, BiConsumer) is a bit very clumsy and contains mutable operations such as Map::put and Map::remove which I would like to avoid:

// // 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
);

Is there a solution using an appropriate combination of Collectors within one Stream::collect call (e.g. without mutable operations)?

like image 703
Nikolas Charalambidis Avatar asked Jan 06 '20 13:01

Nikolas Charalambidis


Video Answer


1 Answers

You can use 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));
}
like image 108
MikeFHay Avatar answered Oct 06 '22 00:10

MikeFHay