В качестве отправной точки, преобразование использовать computeIfAbsent
и merge
дает нам следующее:
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
final Map<K1, Map<K2, Long>> result = new HashMap<>();
for (final Map<K1, Map<K2, Long>> map : valueList) {
for (final Map.Entry<K1, Map<K2, Long>> sub : map.entrySet()) {
for (final Map.Entry<K2, Long> subsub : sub.getValue().entrySet()) {
result.computeIfAbsent(sub.getKey(), k1 -> new HashMap<>())
.merge(subsub.getKey(), subsub.getValue(), Long::sum);
}
}
}
return result;
}
Это снимает большую часть логики от вашего внутреннего цикла.
Этот код ниже wrong, я оставляю его здесь для справки.
Преобразование в API Stream
API не позволит сделать его более аккуратным, но дает вам возможность двигаться вперед.
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
return valueList.stream()
.flatMap(v -> v.entrySet().stream())
.collect(groupingBy(Entry::getKey, collectingAndThen(mapping(Entry::getValue, toList()), l -> l.stream()
.reduce(new HashMap<>(), (l2, r2) -> {
r2.forEach((k, v) -> l2.merge(k, v, Long::sum);
return l2;
}))));
}
Это то, что мне удалось придумать - это ужасно. Проблема в том, что с подходом foreach
у вас есть ссылка на каждый уровень итерации - это делает логику простой. При функциональном подходе вам необходимо учитывать каждую операцию складывания отдельно.
Как это работает?
Мы сначала stream()
наш List<Map<K1, Map<K2, Long>>>
, давая Stream<Map<K1, Map<K2, Long>>>
. Затем мы получаем flatMap
каждый элемент, давая Stream<Entry<K1, Map<K2, Long>>>
- поэтому мы сглаживаем первое измерение. Но мы не можем сгладить, так как нам нужно значение K1
.
Итак, мы используем collect(groupingBy)
на K1
, что дает нам Map<K1, SOMETHING>
- что-то?
Ну, сначала мы используем mapping(Entry::getValue, toList())
, чтобы дать нам Map<K1, List<Map<K2, Long>>>
. Затем мы используем collectingAndThen
, чтобы принять это List<Map<K2, Long>>
и уменьшить его. Обратите внимание, что это означает, что мы производим промежуточный List
, который является расточительным - вы можете обойти это, используя пользовательский номер Collector
.
Для этого мы используем List.stream().reduce(a, b)
, где a
- начальное значение, а b
- операция «сгиба». a
установлено в new HashMap<>()
и b
принимает два значения: либо начальное значение, либо результат предыдущего применения функции и текущего элемента в List
. Поэтому мы для каждого элемента в List
используют Map.merge
для объединения значений.
Я бы сказал, что этот подход более или менее неразборчив - вы не сможете расшифровать его через несколько часов, не говоря уже о нескольких днях.
Итак, все карты одинаковы - то есть имеют одинаковые ключи на обоих уровнях? –
Это не служба перевода кода. Вам нужно показать нам, что вы уже пробовали, чтобы мы могли сказать вам, что вы делаете неправильно. – explv
Сначала вы должны переосмыслить свой первоначальный подход, то есть, что нужно перебирать во внешнем цикле и во внутреннем цикле. – Holger