Собрать groupBy по глубокому свойству
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.stream()
.filter(square -> {
if (square.getZuloCodes().isEmpty()) {
LOG("Ignored {}", square.id);
return false;
}
return true;
})
.forEach(square -> {
square.getZuloCodes()
.forEach(code -> {
res.putIfAbsent(code, new HashSet<>());
res.get(code).add(square);
}));
});
return Collections.unmodifiableMap(res);
}
Код выше получает список квадратов, и эти квадраты могут содержать ZuloCodes внутри. Выходными данными должны быть неизменяемый код zuloCode карты и значение всех квадратов с этим уникальным префиксом. Как вы можете видеть, я не могу найти способ удалить вспомогательный res коллекции и сделать код легко читаемым, есть ли способ разбить эту коллекцию на [zuloCode, square], а затем collect.groupBy? Кроме того, что если внутри фильтра так не читается, как бы вы справились с этим?
2 ответа
Стандартный подход использует flatMap
перед сбором с помощью groupingBy
, но так как вам нужен оригинал Square
для каждого элемента необходимо сопоставить объекту, содержащему оба, Square
Экземпляр и код Зуло String
,
Поскольку в Java нет стандартной пары или типа кортежа (пока), обходным путем является использование Map.Entry
например, вот так
private Map<String, Set<Square>> populateZuloSquare0(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.flatMap(square -> square.getZuloCodes().stream()
.map(code -> new AbstractMap.SimpleEntry<>(code, square)))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toSet())),
Collections::unmodifiableMap));
}
private static boolean logMismatch(Square square, boolean match) {
if(!match) LOG("Ignored {}", square.id);
return match;
}
Альтернативой является использование пользовательского сборщика, который будет перебирать ключи:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.collect(Collector.of(
HashMap<String, Set<Square>>::new,
(m,square) -> square.getZuloCodes()
.forEach(code -> m.computeIfAbsent(code, x -> new HashSet<>()).add(square)),
(m1,m2) -> {
if(m1.isEmpty()) return m2;
m2.forEach((key,set) ->
m1.merge(key, set, (s1,s2) -> { s1.addAll(s2); return s1; }));
return m1;
},
Collections::unmodifiableMap)
);
}
Обратите внимание, что этот пользовательский сборщик может рассматриваться как вариант параллельного кода следующего цикла:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.forEach(square -> {
if(square.getZuloCodes().isEmpty()) LOG("Ignored {}", square.id);
else square.getZuloCodes().forEach(
code -> res.computeIfAbsent(code, x -> new HashSet<>()).add(square));
});
return Collections.unmodifiableMap(res);
}
что может показаться не таким уж плохим сейчас, когда вам не нужен код для параллельной работы...
Как насчет этого. Вы можете использовать map
merge
операция, чтобы сделать это. Я обновил фильтр и упростил его тоже.
squares.stream().filter(s -> !s.getZuloCodes().isEmpty())
.forEach(s -> s.getZuloCodes().stream().forEach(z -> res.merge(z, new HashSet<>(Arrays.asList(s)),
(s1, s2) -> Stream.of(s1, s2).flatMap(Collection::stream).collect(Collectors.toSet()))));