如何从地图中生成具有不同值的地图(并使用 BinaryOperator 使用正确的键)?
How to produce map with distinct values from a map (and use the right key using BinaryOperator)?
我有一张地图 Map<K, V>
,我的目标是删除重复值并再次输出完全相同的结构 Map<K, V>
。如果发现重复值,则必须从保存这些值的两个键(k1
和 k2
)中选择一个键(k
),因此,假设 BinaryOperator<K>
从 k1
得到 k
并且 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
);
是否有在一个 Stream::collect
调用中使用 Collectors
的适当组合的解决方案(例如,没有可变操作)?
试试这个:简单的方法是反转键和值,然后使用 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));
您可以使用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));
}
我发现非流解决方案更具表现力:
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
来保留原始条目顺序。
我找到了一种只使用 Collectors
的方法,不需要再次收集和进一步处理返回的 Map。思路是:
将 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]}
使用 BinaryOperator<K>
.
将新密钥 (List<K>
) 减少到 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}
再次将 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=apple, 4=orange}
最终代码:
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
)
)
)
);
通过 "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));
我有一张地图 Map<K, V>
,我的目标是删除重复值并再次输出完全相同的结构 Map<K, V>
。如果发现重复值,则必须从保存这些值的两个键(k1
和 k2
)中选择一个键(k
),因此,假设 BinaryOperator<K>
从 k1
得到 k
并且 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
);
是否有在一个 Stream::collect
调用中使用 Collectors
的适当组合的解决方案(例如,没有可变操作)?
试试这个:简单的方法是反转键和值,然后使用 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));
您可以使用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));
}
我发现非流解决方案更具表现力:
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
来保留原始条目顺序。
我找到了一种只使用 Collectors
的方法,不需要再次收集和进一步处理返回的 Map。思路是:
将
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]}
使用
将新密钥 (BinaryOperator<K>
.List<K>
) 减少到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}
再次将
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=apple, 4=orange}
最终代码:
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
)
)
)
);
通过 "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));