Объединение двух карт <String, Integer> с Java 8 Stream API
У меня есть два (или более) Map<String, Integer>
объектов. Я хотел бы объединить их с Java 8 Stream API таким образом, чтобы значения для общих ключей были максимальными значениями.
@Test
public void test14() throws Exception {
Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3);
Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4);
List<Map<String, Integer>> list = newArrayList(m1, m2);
Map<String, Integer> mx = list.stream()... // TODO
Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4);
assertEquals(expected, mx);
}
Как я могу сделать этот метод проверки зеленым?
Я играл с collect
и Collectors
некоторое время без каких-либо успехов.
(ImmutableMap
и newArrayList
принадлежат Google Guava.)
Ответы
Ответ 1
@Test
public void test14() throws Exception {
Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3);
Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4);
Map<String, Integer> mx = Stream.of(m1, m2)
.map(Map::entrySet) // converts each map into an entry set
.flatMap(Collection::stream) // converts each set into an entry stream, then
// "concatenates" it in place of the original set
.collect(
Collectors.toMap( // collects into a map
Map.Entry::getKey, // where each entry is based
Map.Entry::getValue, // on the entries in the stream
Integer::max // such that if a value already exist for
// a given key, the max of the old
// and new value is taken
)
)
;
/* Use the following if you want to create the map with parallel streams
Map<String, Integer> mx = Stream.of(m1, m2)
.parallel()
.map(Map::entrySet) // converts each map into an entry set
.flatMap(Collection::stream) // converts each set into an entry stream, then
// "concatenates" it in place of the original set
.collect(
Collectors.toConcurrentMap( // collects into a map
Map.Entry::getKey, // where each entry is based
Map.Entry::getValue, // on the entries in the stream
Integer::max // such that if a value already exist for
// a given key, the max of the old
// and new value is taken
)
)
;
*/
Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4);
assertEquals(expected, mx);
}
Ответ 2
Map<String, Integer> mx = new HashMap<>(m1);
m2.forEach((k, v) -> mx.merge(k, v, Integer::max));
Ответ 3
mx = list.stream().collect(HashMap::new,
(a, b) -> b.forEach((k, v) -> a.merge(k, v, Integer::max)),
Map::putAll);
Это охватывает общий случай для любого списка размеров и должен работать с любыми типами, просто поменяйте Integer::max
и/или HashMap::new
по желанию.
Если вам не важно, какое значение появляется в слиянии, существует гораздо более чистое решение:
mx = list.stream().collect(HashMap::new, Map::putAll, Map::putAll);
И как общие методы:
public static <K, V> Map<K, V> mergeMaps(Stream<? extends Map<K, V>> stream) {
return stream.collect(HashMap::new, Map::putAll, Map::putAll);
}
public static <K, V, M extends Map<K, V>> M mergeMaps(Stream<? extends Map<K, V>> stream,
BinaryOperator<V> mergeFunction, Supplier<M> mapSupplier) {
return stream.collect(mapSupplier,
(a, b) -> b.forEach((k, v) -> a.merge(k, v, mergeFunction)),
Map::putAll);
}
Ответ 4
Я добавил свой вклад в библиотеку протонных пакетов , которая содержит утилиты для Stream API. Здесь, как вы могли бы достичь того, чего хотите:
Map<String, Integer> mx = MapStream.ofMaps(m1, m2).mergeKeys(Integer::max).collect();
В принципе mergeKeys
будет собирать пары ключ-значение на новой карте (предоставление функции слияния необязательно, в противном случае вы получите Map<String, List<Integer>>
) и вызовите stream()
в entrySet()
, чтобы получить новый MapStream
. Затем используйте collect()
, чтобы получить полученную карту.
Ответ 5
Используя StreamEx, вы можете:
StreamEx.of(m1, m2)
.flatMapToEntry(x -> x)
.grouping(IntCollector.max())
Ответ 6
Это по инженерному делу, вы можете сделать только:
map3 = new HashMap<>();
map3.putAll(map1);
map3.putAll(map2);