有没有办法使用"groupingBy"为嵌套结构中的MULTIPLE元素收集地图?

Gho*_*ica 6 java grouping java-8 java-stream

首先,一些上下文代码:

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);
Run Code Online (Sandbox Code Playgroud)

以上代表我正在处理的数据结构.我有一个外部地图(键类型无关紧要),它包含内部"属性映射"作为值.这些内部映射使用字符串来查找不同类型的数据.

在我正在处理的情况下,每个v1,v2,...代表一个"磁盘".每个磁盘都有特定的大小,但可以有多个父级.

现在我需要将每个父ID 的大小总结为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())));
Run Code Online (Sandbox Code Playgroud)

但代码感觉非常笨重(就像我需要第二张地图来添加大小).

我有一个类似的情况,总是只有一个父,并且可以很容易地使用

collect(Collectors.groupingBy(...), Collectors.summingDouble(...);
Run Code Online (Sandbox Code Playgroud)

但我因"多重"父母案而迷失.

那么,问题:可以使用上面的转换计算所需的Map<Id, Double>重写groupingBy()吗?

只是为了记录:上面只是我需要答案的问题的一个mcve.据我所知,"数据布局"可能看起来很奇怪.实际上,我们实际上有不同的类代表这些"磁盘".但是我们的"框架"还允许使用这些ID和属性名访问数据库中任何对象的属性.有时,当您遇到性能问题时,与访问真正的"磁盘"对象本身相比,以这种"原始属性映射"方式获取数据的速度要快几个数量级.换句话说:我无法改变任何有关上下文的内容.我的问题只是重写计算.

(我受限于Java8和"标准"Java库,但是对于新版本Java或者解决此问题的非标准方法的其他答案也将受到赞赏)

Era*_*ran 5

这是一个单流管道解决方案:

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)));
Run Code Online (Sandbox Code Playgroud)

输出:

{B=3.0, A=1.0, C=2.0}
Run Code Online (Sandbox Code Playgroud)

我们的想法是将每个内部转换Map为一个Stream条目,其中键是Id("父" Set),并且值是相应的"大小".

然后很容易将其分组Stream到所需的输出中.

  • 对于较大的数据集,在流式传输`(Set <IdWrapper>)m.get("parents")`而不是重复它之前,可能值得做一次`(Double)m.get("size")`每个元素. (2认同)