Java 8 List<Map<String, Object>> 转换为按键分组并按值计数的 List<Map<String, Object>>

8
我有以下地图列表。
List<Map<String, Object>> listBeforeGroup = new ArrayList<Map<String, Object>>();

    Map<String, Object> m1 = new HashMap<String, Object>();
    m1.put("company", "LG");
    m1.put("billType", "A");
    m1.put("billPeriod", "09-2018");

    Map<String, Object> m2 = new HashMap<String, Object>();
    m2.put("company", "LG");
    m2.put("billType", "A");
    m2.put("billPeriod", "09-2018");

    Map<String, Object> m3 = new HashMap<String, Object>();
    m3.put("company", "LG");
    m3.put("billType", "A");
    m3.put("billPeriod", "09-2018");

    Map<String, Object> m4 = new HashMap<String, Object>();
    m4.put("company", "LG");
    m4.put("billType", "B");
    m4.put("billPeriod", "01-2019");

    Map<String, Object> m5 = new HashMap<String, Object>();
    m5.put("company", "LG");
    m5.put("billType", "B");
    m5.put("billPeriod", "01-2019");

    Map<String, Object> m6 = new HashMap<String, Object>();
    m6.put("company", "Samsung");
    m6.put("billType", "A");
    m6.put("billPeriod", "10-2018");

    Map<String, Object> m7 = new HashMap<String, Object>();
    m7.put("company", "Samsung");
    m7.put("billType", "A");
    m7.put("billPeriod", "10-2018");

    Map<String, Object> m8 = new HashMap<String, Object>();
    m8.put("company", "Samsung");
    m8.put("billType", "B");
    m8.put("billPeriod", "11-2018");

    listBeforeGroup.add(m1);listBeforeGroup.add(m2);
    listBeforeGroup.add(m3);listBeforeGroup.add(m4);
    listBeforeGroup.add(m5);listBeforeGroup.add(m6);

我该如何获得这个输出?
    //Desired Output - List<Map<String, Object>>
    //{company=LG, billType=A, billPeriod=09-2018, count=3}
    //{company=LG, billType=B, billPeriod=01-2019, count=2}
    //{company=Samsung, billType=A, billPeriod=10-2018, count=2}
    //{company=Samsung, billType=B, billPeriod=11-2018, count=1}

我尝试使用Java 8 Streams,但是我无法得到所需的输出。
List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream().map(m -> m.entrySet().stream().collect(Collectors.toMap(p -> p.getKey(), p - >  p.getValue()))).collect(Collectors.toList());

顺便尝试了一下这个方法,它可以生成一个地图,但我不需要这个。
Map<Object, Long> listAfterGroup = listBeforeGroup.stream().flatMap(m -> m.entrySet().stream()).collect(Collectors.groupingBy(Map.Entry::getKey,Collectors.counting()));

我希望按照“billPeriod”这个键将地图分组,然后根据值计算项目数量,最后生成一个新的地图列表。


2
我建议在这里采用更多的面向对象方法。 - T McKeown
3个回答

4
您可以创建一个名为Company的类,这样后续的操作就会变得更加简单。
class Company {
    String company;
    String billType;
    String billPeriod;

    public Company(String company, String billType, String billPeriod) {
        this.company = company;
        this.billType = billType;
        this.billPeriod = billPeriod;
    }

    // getters, setters, toString, etc
}

初始化列表:

List<Company> list = new ArrayList<>();
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "B", "01-2019"));
list.add(new Company("LG", "B", "01-2019"));
list.add(new Company("Samsung", "A", "10-2018"));
list.add(new Company("Samsung", "A", "10-2018"));
list.add(new Company("Samsung", "B", "11-2018"));

现在举个例子,您可以按公司名称分组 :
Map<String, Long> map = list.stream().collect(
        Collectors.groupingBy(Company::getCompany, 
                              Collectors.mapping((Company c) -> c, Collectors.counting())));

现在,您可以更轻松地执行所需的其他操作了。此外,这里不再需要创建8个映射表,而是只需处理1个列表


1
这是一个很好的建议,可以引入新的对象。 - mwdev

2

对于地图的分组和计数来说,由于计数值增加后地图数据会发生变化,所以需要保存原始地图数据,并将计数值保存到另一个地图中。在计数过程完成后再将两个地图合并。

最初的回答:

Map<Map<String, Object>, Long> counterData = listBeforeGroup.stream().collect(Collectors.groupingBy(m -> m, Collectors.counting()));

List<Map<String, Object>> listAfterGroup = new ArrayList<>();
for (Map<String, Object> m : counterData.keySet()) {
    Map<String, Object> newMap = new HashMap<>(m);
    newMap.put("count", counterData.get(m));
    listAfterGroup.add(newMap);
}

更新Java 8的方法,调试不易

List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream().collect(Collectors.groupingBy(m -> m, Collectors.counting())).entrySet().stream().map(e -> {
    Map<String, Object> newMap = e.getKey();
    newMap.put("count", e.getValue());
    return newMap;
}).collect(Collectors.toList());

我认为你的Java8解决方案不正确,你测试过吗?在你的代码中,groupingBy(m->m假设两个具有相同键和元素集的Java映射是相等的,这是不正确的。因此,结果将是所有计数=1。 - mwdev
我已经测试了Java 8的解决方案,它可以正常工作。 输出 [{billType=A, count=3, company=LG, billPeriod=09-2018}, {billType=B, count=2, company=LG, billPeriod=01-2019}, {billType=A, count=1, company=Samsung, billPeriod=10-2018}] - jhuamanchumo
那么我喜欢他的解决方案 +1 - mwdev

0

我的Java 8方法:

    Function<Map<String, Object>, String> createHashKey = map ->
                     map.values().stream()
                    .map(val -> String.valueOf(val))
                    .collect(Collectors.joining());

    BinaryOperator<Map<String, Object>> mergeDuplicate = (map1, map2) -> {
        int incrementedCount = (int)map1.get("count") + 1;
        map1.put("count", incrementedCount);
        return map1;
    };

    List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream()
            .collect(Collectors.toMap(createHashKey, el -> {
                        el.put("count", 1);
                        return el;
            },mergeDuplicate))
          .values().stream()
          .collect(toList());

也许不是最简洁的解决方案,但我认为代码的逻辑非常易读且易于理解。


1
我已经测试了这种方法,但它并不起作用。 输出: [{billType=A,count=6,company=LG,billPeriod=09-2018}] - jhuamanchumo
小错误,刚刚修正了代码 - 感谢您的检查!;] - mwdev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接