Собрать 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);
}

что может показаться не таким уж плохим сейчас, когда вам не нужен код для параллельной работы...

Как насчет этого. Вы можете использовать mapmerge операция, чтобы сделать это. Я обновил фильтр и упростил его тоже.

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()))));
Другие вопросы по тегам