Объединить список карт в одну карту путем суммирования значений
У меня есть класс Значения:
public class Values {
private int count;
private int values;
}
И список нескольких карт типа Map<String, Values>
Map<String, Values> map1 = new HashMap<>();
map1 .put("aaa", new Values(1, 10));
map1 .put("bbb", new Values(5, 50));
map1 .put("ccc", new Values(2, 30));
Map<String, Values> map2= new HashMap<>();
map2.put("aaa", new Values(2, 20));
map2.put("bbb", new Values(3, 50));
map2.put("ccc", new Values(3, 10));
List<Map<String, Values>> list = Arrays.asList(map1, map2);
Там может быть любое количество карт и любое количество записей внутри карт, но ключи для карт всегда одинаковы, только значения могут отличаться. Мой пример содержит только 2 карты и только 3 записи для каждой карты для ясности.
Я хочу получить одну карту с теми же ключами и объектами Values, как сумма каждого "количества" и каждого "значения" в объектах, например:
{
aaa= {
count = 3,
value = 30
},
bbb= {
count = 8,
value = 100
},
ccc= {
count = 6,
value = 40
}
}
Я пытаюсь добиться этого с помощью Streams API, но я застрял:
public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
return maps.stream()
.flatMap(m -> m.entrySet().stream()) ...?
}
Как я могу сгруппировать каждую запись по их ключам и сложить каждый счетчик и каждое значение в одну карту?
Заранее спасибо.
Ответы
Ответ 1
Вы можете собирать записи вашего Stream
с помощью сборщика toMap
, с функцией слияния.
public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
return maps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(v1,v2) -> new Values(v1,v2)));
}
Предполагая, что у вас есть конструктор Values
который принимает два экземпляра Values
и создает экземпляр, имеющий суммы значений.
Конечно, вы можете написать функцию слияния без этого конструктора. Например:
(v1,v2) -> new Values(v1.getCount()+v2.getCount(),v1.getValue()+v2.getValue())
Ответ 2
Еще одно решение с группировкой:
Map<String, Optional<Values>> collect = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue,
reducing((v1, v2) -> new Values(v1.count + v2.count, v1.values + v2.values)))));
Примечание: значения этой карты являются Optional<Values>
. Если у вас есть null
значение на одной из ваших исходных карт, например map2.put("ddd", null);
это позволяет избежать NullPointerException
и вернуть Optional.empty