Есть ли способ собрать карту, используя «groupingBy» для нескольких элементов внутри вложенной структуры? - PullRequest
0 голосов
/ 22 ноября 2018

Сначала немного кода контекста:

import java.util.*;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.function.Function;
import java.util.stream.Collectors;

class Scratch {

  static enum Id {A, B, C}
  static class IdWrapper {
    private final Id id;
    public IdWrapper(Id id) {this.id = id;}
    Id getId() { return id; }
  }

  public static void main(String[] args) {
    Map<String, Object> v1 = new HashMap<>();
    v1.put("parents", new HashSet<>(Arrays.asList(new IdWrapper(Id.A), new IdWrapper(Id.B))));
    v1.put("size", 1d);

    Map<String, Object> v2 = new HashMap<>();
    v2.put("parents", new HashSet<>(Arrays.asList(new IdWrapper(Id.B), new IdWrapper(Id.C))));
    v2.put("size", 2d);

    Map<String, Map<String, Object>> allVs = new HashMap<>();
    allVs.put("v1", v1);
    allVs.put("v2", v2);

Выше представлена ​​структура данных, с которой я имею дело.У меня есть внешняя карта (тип ключа не имеет значения), которая содержит внутренние «карты свойств» в качестве значений.Эти внутренние карты используют строки для поиска различного рода данных.

В случае, над которым я работаю, каждый v1, v2, ... представляет "диск".Каждый диск имеет определенный размер, но может иметь несколько родителей.

Теперь мне нужно суммировать размеры на родительский идентификатор как Map<Id, Double>.Для приведенного выше примера эта карта будет {B=3.0, A=1.0, C=2.0}.

Следующий код дает ожидаемый результат:

    HashMap<Id, DoubleAdder> adders = new HashMap<>();
    allVs.values().forEach(m -> {
        double size = (Double) m.get("size");
        Set<IdWrapper> wrappedIds = (Set<IdWrapper>) m.get("parents");
        wrappedIds.forEach(w -> adders.computeIfAbsent(w.getId(), a -> new DoubleAdder()).add(size));
    });

    System.out.println(adders.keySet().stream()
            .collect(Collectors.toMap(Function.identity(), key -> adders.get(key).doubleValue())));

Но код выглядит довольно неуклюжим (как тот факт, что мне нуженвторая карта для сложения размеров).

У меня есть похожий случай, когда всегда есть ровно один родитель, и это легко можно решить с помощью

collect(Collectors.groupingBy(...), Collectors.summingDouble(...);

Но я проиграл в случае с «множественными» родителями.

Итак, вопрос: можно ли переписать приведенное выше преобразование для вычисления требуемого Map<Id, Double>, используя groupingBy()?

И просто для записи: приведенное выше - просто mcve для задачи Iнужен ответ для.Я понимаю, что «макет данных» может выглядеть странно.На самом деле, у нас есть отдельные классы, представляющие, например, эти «диски».Но наша «структура» также позволяет получить доступ к свойствам любого объекта в базе данных, используя такие идентификаторы и имена свойств.И иногда, когда у вас возникают проблемы с производительностью, выборка данных таким способом «карты необработанных свойств» на несколько порядков быстрее по сравнению с доступом к самим настоящим «дисковым» объектам.Другими словами: я не могу ничего изменить в контексте.Мой вопрос касается только переписывания этих вычислений.

(я ограничен Java8 и «стандартными» библиотеками Java, но дополнительные ответы для более новых версий Java или хорошие нестандартные способы решения этой проблемыбудет оценен тоже)

1 Ответ

0 голосов
/ 22 ноября 2018

Вот решение для однопоточного конвейера:

Map<Id,Double> sums = allVs.values ()
                           .stream () 
                           .flatMap (m -> ((Set<IdWrapper>)m.get ("parents")).stream ()
                                                                             .map (i -> new SimpleEntry<Id,Double>(i.getId(),(Double)m.get ("size"))))
                           .collect (Collectors.groupingBy (Map.Entry::getKey,
                                                            Collectors.summingDouble (Map.Entry::getValue)));

Вывод:

{B=3.0, A=1.0, C=2.0}

Идея состоит в том, чтобы преобразовать каждый внутренний Map в Stream записей, где ключэто Id (из «родителей» Set), а значение соответствует соответствующему «размеру».

Тогда можно просто сгруппировать Stream в нужный вывод.

...