【问题标题】:Java 8 - How to group and sum on a list of maps using stream collectorsJava 8 - 如何使用流收集器对地图列表进行分组和求和
【发布时间】:2015-07-10 00:06:45
【问题描述】:

我有一个地图列表,想对某些列进行分组和求和。

地图列表:

double min=100;
double max=999;
List<Map<String,Object>> positions = new ArrayList<Map<String,Object>>();
for (int i=0; i<10; i++) {
    Map<String,Object> positionRow = new HashMap<String,Object>();
    positionRow.put("id", i+1);
    if ((i+1)%2 == 0)
        positionRow.put("Mod", "Even");
    else
        positionRow.put("Mod", "Odd");
    Random r = new Random();
    double randomValue = min + (max - min) * r.nextDouble();
    positionRow.put("price", randomValue);
    positionRow.put("bigger", (randomValue > 300)?"Y":"N");
    positions.add(positionRow);
}

我正在尝试将 Java 8 流与 Collectors.groupingBy 和 Collectors.summingDouble 一起使用,以获得Map&lt;String,Map&lt;String,Object&gt;&gt; 的结果。到目前为止,我可以通过使用带有 Collectors.groupingBy 的 Java 8 流获得Map&lt;String,List&lt;Map&lt;String,Object&gt;&gt;&gt;

按“Mod”和“更大”分组:

Map<String, List<Map<String, Object>>> grouped = positions.stream()
        .collect(
                Collectors.groupingBy(m -> m.get("Mod").toString()
                        + "<|>" + m.get("bigger").toString()));

按“Mod”和“更大”结果分组:

{Even<|>Y=[{Mod=Even, price=872.729803251601, bigger=Y, id=2}, {Mod=Even, price=353.6589309614915, bigger=Y, id=4}, {Mod=Even, price=981.8179976373482, bigger=Y, id=6}, {Mod=Even, price=942.3538966530067, bigger=Y, id=8}, {Mod=Even, price=919.0174189044218, bigger=Y, id=10}], Odd<|>Y=[{Mod=Odd, price=663.7589137270676, bigger=Y, id=1}, {Mod=Odd, price=894.1766799283644, bigger=Y, id=3}, {Mod=Odd, price=905.8003509608488, bigger=Y, id=5}, {Mod=Odd, price=758.3085768816934, bigger=Y, id=7}, {Mod=Odd, price=531.1035747782346, bigger=Y, id=9}]}

我期待 Group by "Mod" 和 "bigger" 和 sum on "id" 和 "price" 结果(Map&lt;String,Map&lt;String,Object&gt;&gt;):

{Even<|>Y={sum_price=4069.578047, sum_id=30}, Odd<|>Y={sum_price=3753.148096,sum_id=25}}

有什么建议吗?谢谢,

【问题讨论】:

    标签: lambda java-8 java-stream


    【解决方案1】:

    Map&lt;String, Object&gt; 不是一个很好的对象维护类型。这正是发明了具有名称和声明类型的类和字段的原因。收集器所需的代码反映了这一点:

    Map<String, Map<String, Object>> grouped = positions.stream()
    .collect(Collectors.groupingBy(m -> m.get("Mod")+"<|>"+m.get("bigger"),
        Collector.of(HashMap::new, (m,p)-> {
            m.merge("sum_price", p.get("price"), (a,b)->((Double)a)+((Double)b));
            m.merge("sum_id", p.get("id"), (a,b)->((Integer)a)+((Integer)b));
        }, (m1,m2)-> {
            m1.merge("sum_price", m2.get("sum_price"), (a,b)->((Double)a)+((Double)b));
            m1.merge("sum_id", m2.get("sum_id"), (a,b)->((Integer)a)+((Integer)b));
            return m1;
        })));
    

    【讨论】:

    • 它有效。但是 Collector.of 的 java.util.function.BinaryOperator 有什么作用呢?我试过没有任何逻辑 (m1,m2) -> {return m1;} 它仍然有效。
    • 组合器通常只用于并行流。然后,每个线程将使用第一个参数(供应商)创建一个本地容器,第二个函数将项目收集到本地容器中,一旦两个线程处理了它们的部分流,它们将使用第三个函数合并两个容器(这里的容器类型是Map)。所以这里没有使用它,但你应该决定,仍然提供一个工作函数或提供一个抛出函数来检测缺失的支持。不要提供一个一旦被使用就会静默中断的函数……
    【解决方案2】:

    您想将两个独立的总和收集到一个结果中。为此,您可以使用我在this 中写的“配对”Collector 答案:

    Collector<Map<String, Object>, ?, Map<String, Object>> collector = 
            pairing(
                    Collectors.summingDouble(m -> (Double)m.get("price")), 
                    Collectors.summingInt(m -> (Integer)m.get("id")), 
            (sumPrice, sumId) -> {
                Map<String, Object> res = new HashMap<>();
                res.put("sum_price", sumPrice);
                res.put("sum_id", sumId);
                return res;
            });
    Map<String, Map<String, Object>> grouped = positions.stream()
            .collect(
                    Collectors.groupingBy(m -> m.get("Mod").toString()
                            + "<|>" + m.get("bigger").toString(), collector));
    

    请注意,此收集器在我的 StreamEx 库中随时可用。使用我的库的其他功能,解决方案可以更短:

    Collector<Map<String, Object>, ?, Map<String, Object>> collector = 
            MoreCollectors.pairing(
                    Collectors.summingDouble(m -> (Double)m.get("price")), 
                    Collectors.summingInt(m -> (Integer)m.get("id")), 
            (sumPrice, sumId) -> EntryStream.<String, Object>of
                    ("sum_price", sumPrice, "sum_id", sumId).toMap());
    Map<String, Map<String, Object>> grouped = StreamEx.of(positions)
            .groupingBy(m -> m.get("Mod") + "<|>" + m.get("bigger"), collector);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多