如何从地图生成具有不同值的地图(并使用BinaryOperator使用右键)?


13

我有一张地图Map<K, V>,我的目标是删除重复的值并Map<K, V>再次输出相同的结构。如果发现重复的值,则必须k从两个键(k1k1)中选择一个保存这些值的键(),因此,假定BinaryOperator<K>提供kk1k2可用。

输入和输出示例:

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

是否有一个解决方案可以Collectors在一个Stream::collect调用中使用适当的组合(例如,没有可变操作)?


2
您对“ 更好 ”或“ 最好 ”的衡量标准是什么?是否必须通过Streams 完成?
Turing85

如果相同的值与2个键相关联,如何选择保留哪个键?
迈克尔

您的情况下的预期输出是多少?
YCF_L

1
@ Turing85:正如我所说。该更好最好是没有明确使用可变映射方法,如Map::putMap::removeCollector
Nikolas

Answers:


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和Michael,这是因为他必须合并密钥,因此反转对将合并密钥。然后缺少的是第二次反转。
Jean-BaptisteYunès

2
@HadiJ不!反转是正确的!但是第二回是必须的。合并用于合并键,但只能用于值合并...
Jean-BaptisteYunès

@Jean-BaptisteYunès我知道需要合并,但是为什么我不能立即得到呢,是因为您编码swap(); collect(key, value, binOp);而不是collect(value, key, binOp)。也许我需要在jshell中实际尝试一下?
GPI

2
随意使用您共享的代码中问题中引入的局部变量。如果在回答时与意图有冲突,请还原。
纳曼(Naman)

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
Nikolas

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],橙色= [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,橙色= 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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.