【问题标题】:Is there a way to collect a map using "groupingBy" for MULTIPLE elements within a nested structure?有没有办法使用“groupingBy”为嵌套结构中的多个元素收集地图?
【发布时间】:2018-11-22 08:39:27
【问题描述】:

首先,一些上下文代码:

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、... 都代表一个“磁盘”。每个磁盘都有特定的大小,但可以有多个父级。

现在我需要将大小 per 父 ID 总结为 Map&lt;Id, Double&gt;。 对于上面的示例,该映射将是 {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&lt;Id, Double&gt; 的转换可以用groupingBy() 重写吗?

仅作记录:以上只是我需要答案的问题的 mcve。我知道“数据布局”可能看起来很奇怪。实际上,例如,我们实际上有代表这些“磁盘”的不同类。但我们的“框架”还允许使用此类 ID 和属性名称访问数据库中任何对象的属性。有时,当您遇到性能问题时,与访问真正的“磁盘”对象本身相比,以这种“原始属性映射”方式获取数据要快几个数量级。换句话说:我不能改变任何关于上下文的东西。我的问题完全是关于重写该计算。

(我受限于 Java8 和“标准”Java 库,但对于较新版本的 Java 或解决此问题的非标准方法的附加答案将不胜感激)

【问题讨论】:

  • 在我正在处理的情况下,每个 v1、v2、... 代表一个“磁盘” 然后创建一个具有两个类型属性的 Disk 类,而不是使用 Map .
  • @JBNizet 查看我的更新。以上只是一个例子,实际情况要复杂得多。实际上,使用上述地图方法是有充分理由的(full 查找需要 50 毫秒,而 3 秒......这是一个小配置)

标签: java java-8 java-stream grouping


【解决方案1】:

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

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 分组到所需的输出中。

【讨论】:

  • 我也有这个想法,但不知道怎么去那里!
  • 对于更大的数据集,在流式传输到 (Set&lt;IdWrapper&gt;)m.get ("parents") 之前只执行一次 (Double)m.get ("size") 可能值得,而不是为每个元素重复它。
  • @Holger 这似乎是一个小优化,因为这些内部 Maps 只有 2 个键。如果实际的Maps 更大,它可能会有所帮助,尽管不多(因为无论如何get 的预期时间都是不变的)。
  • 内部对象是Sets,而不是Maps,但是是的,我已经说过“用于更大的数据集”。该操作有恒定的时间,但散列并不一定便宜(类型转换不是什么大问题),它乘以集合元素的数量。
  • @Holger 我指的是内部的Map&lt;String, Object&gt;,而不是那些Maps 中的Set&lt;IdWrapper&gt;s。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-06
  • 2019-11-09
  • 1970-01-01
相关资源
最近更新 更多