Lambda Java 8-如何使用流收集器对地图列表进行分组和求和

Lambda Java 8-如何使用流收集器对地图列表进行分组和求和,lambda,java-8,java-stream,Lambda,Java 8,Java Stream,我有一个地图列表,想对某些列进行分组和求和 地图列表: 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<Stri

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

地图列表:

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);
}
double min=100;
双倍最大值=999;
列表位置=新的ArrayList();
对于(int i=0;i 300)?“Y”:“N”);
位置。添加(位置行);
}
我正在尝试将Java 8 stream与Collectors.groupingBy和Collectors.summingDouble一起使用,以获得
Map
的结果。到目前为止,我可以通过使用Java8流和Collectors.groupingBy来获得
Map

按“Mod”和“biger”分组:

Map group=positions.stream()
.收集(
Collectors.groupby(m->m.get(“Mod”).toString()
+“+m.get(“更大”).toString());
按“Mod”和“biger”结果分组:

{EvenY=[{Mod=偶数,price=872.729803251601,biger=Y,id=2},{Mod=偶数,price=353.6589309614915,biger=Y,id=4},{Mod Mod=偶数,price=981.8179976373482,biger=Y,id=6},{Mod Mod偶,price=942.3538966530067,biger=Y,id=8},{Mod Mod偶数,price=919.01741890418,biger=Y,id=10,OddY=10,OddY=1375676},{,{Mod=Odd,price=894.1766799283644,biger=Y,id=3},{Mod=Odd,price=905.8003509608488,biger=Y,id=5},{Mod=Odd,price=758.3085768816934,biger=Y,id=7},{Mod Odd=Odd,price=531.1035747782346,biger=Y,id=9}]
我希望按“Mod”和“biger”分组,并对“id”和“price”结果求和(
Map
):

{EvenY={sum_price=4069.578047,sum_id=30},OddY={sum_price=3753.148096,sum_id=25}}

有什么建议吗?谢谢,

您想将两个独立的总和收集到一个结果中。要做到这一点,您可以使用“配对”
收集器,我在回答中写道:

收集器=
配对(
收集者。总和双倍(m->(双倍)m.get(“价格”),
收集器.summingit(m->(整数)m.get(“id”),
(sumPrice,sumId)->{
Map res=新的HashMap();
res.put(“总和价格”,总和价格);
res.put(“sum_id”,sumId);
返回res;
});
Map group=positions.stream()
.收集(
Collectors.groupby(m->m.get(“Mod”).toString()
+“+m.get(“更大”).toString(),收集器”);
请注意,此收集器在我的库中随时可用。使用我的库的其他功能,解决方案甚至可以更短:

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);
收集器=
摩尔配对(
收集者。总和双倍(m->(双倍)m.get(“价格”),
收集器.summingit(m->(整数)m.get(“id”),
(sumPrice,sumId)->EntryStream.of
(“sum_-price”,sumPrice,“sum_-id”,sumId.toMap());
地图分组=流量x(位置)
.groupingBy(m->m.get(“Mod”)+“”+m.get(“biger”),收集器);
Map
不是一种很好的对象维护类型。这正是为什么会发明具有名称和声明类型的类和字段。收集器所需的代码反映了这一点:

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;
    })));
Map group=positions.stream()
.collect(收集器.groupingBy(m->m.get(“Mod”)+“”+m.get(“biger”),
Collector.of(HashMap::new,(m,p)->{
m、 合并(“价格总和”,p.get(“价格”),(a,b)->(双倍)a)+(双倍)b);
m、 合并(“sum_id”,p.get(“id”),(a,b)->(整数)a+(整数)b);
},(m1,m2)->{
m1.合并(“总和价格”,m2.获取(“总和价格”),(a,b)->(双倍)a)+(双倍)b);
m1.merge(“sum_id”,m2.get(“sum_id”),(a,b)->(整数)a+(整数)b);
返回m1;
})));

它可以工作。但是Collector.of的java.util.function.BinaryOperator做什么呢?我在没有任何逻辑(m1,m2)->{return m1;}的情况下尝试过,它仍然可以工作。合并器通常只用于并行流。然后,每个线程将使用第一个参数(供应商)要创建一个本地容器和将项目收集到本地容器中的第二个函数,并且一旦两个线程处理了它们的流部分,它们将使用第三个函数合并两个容器(这里的容器类型是
映射
).因此,这里不使用它,但您应该决定使用哪种方法,仍然提供一个工作函数或提供一个抛出函数来检测缺少的支持。不要提供一个一旦使用就会自动中断的函数…
{Even<|>Y={sum_price=4069.578047, sum_id=30}, Odd<|>Y={sum_price=3753.148096,sum_id=25}}
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));
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);
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;
    })));