Java Streams - 分组并返回嵌套映射

标签 java group-by hashmap java-stream collectors

我的数据是这样的,

unitId  time  value1 value2
 a      2021    10    11   
 a      2022    15    13
 b      2021    20    25
 b      2022    30    37

我的目标是将每个unitId和值放入这样的 map 中,

{
  'a': {'2021_value1': 10, '2021_value2': 11, '2022_value1': 15, '2022_value2': 13},
  'b': {'2021_value1': 20, '2021_value2': 25, '2022_value1': 30, '2022_value2': 37},
}

我已经找到了两种方法来实现这一点,这是我的代码,

public class Unit {

    public String unitId;

    public Integer year;

    public Integer value1;

    public Integer value2;

    public static Unit of(String unitId, Integer year, Integer value1, Integer value2) {
        Unit unit = new Unit();
        unit.unitId = unitId;
        unit.year = year;
        unit.value1 = value1;
        unit.value2 = value2;
        return unit;
    }

}

并且,

public class UnitTest {

    private static void printMap(Map<String, Map<String, Integer>> map) {
        map.forEach((k, v) -> {
            String vStr = v.entrySet().stream().map(a -> String.format("%s: %s", a.getKey(), a.getValue())).collect(Collectors.joining(", "));
            System.out.printf("%s: {%s}%n", k, vStr);
        });
    }

    public static void main(String[] args) {
        List<Unit> list = new ArrayList<>();
        list.add(Unit.of("a", 2021, 10,  11 ));
        list.add(Unit.of("a", 2022, 15,  13));
        list.add(Unit.of("b", 2021, 20,  25));
        list.add(Unit.of("b", 2022, 30,  37));

        Map<String, Map<String, Integer>> map1 = list.stream().collect(
            Collectors.groupingBy(
                x -> x.unitId,
                Collector.of(
                    HashMap::new,
                    (x, y) -> {
                        x.put(String.format("%s_%s", y.year, "value1"), y.value1);
                        x.put(String.format("%s_%s", y.year, "value2"), y.value2);
                    },
                    (x, y) -> {x.putAll(y); return x;}
                )
            )
        );

        Map<String, Map<String, Integer>> map2 = list.stream().collect(
            Collectors.groupingBy(
                x -> x.unitId,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    x -> x.stream()
                        .flatMap(y -> Stream.of(
                                    new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value1"), y.value1),
                                    new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value2"), y.value2)
                             ))
                        .collect(Collectors.toMap(
                                     AbstractMap.SimpleEntry::getKey, 
                                     AbstractMap.SimpleEntry::getValue)))
            )
        );
        printMap(map1);
        printMap(map2);
    }
}

第一个更像手动编写处理,第二个使用可能没有必要的临时列表。有没有直接或简单的方法来做到这一点,比如使用Collectors.toMap API 或其他东西?

最佳答案

Is there any direct or simple way to do this, like use Collectors.toMap API or something else?

如果您只想使用内置收集器,您可以尝试组合使用 groupingBy()teeing() .

Collectors.teeing() 需要三个参数:2下游收集器合并 函数。流中的每个元素都将传递到两个收集器中,当这些收集器完成后,它们产生的结果将由函数合并。

在下面的代码中,toMap() 用作 teeing() 的两个下游收集器。每个收集器负责检索其类型。

代码可能如下所示:

public static void main(String[] args) {
    List<Unit> list =
        List.of(Unit.of("a", 2021, 10,  11 ),
                Unit.of("a", 2022, 15,  13),
                Unit.of("b", 2021, 20,  25),
                Unit.of("b", 2022, 30,  37));

    Map<String, Map<String, Integer>> map = list.stream()
        .collect(Collectors.groupingBy(Unit::getUnitId,
            Collectors.teeing(
                Collectors.toMap(
                    unit -> unit.getYear() + "_value1",
                    Unit::getValue1),
            Collectors.toMap(
                    unit -> unit.getYear() + "_value2",
                    Unit::getValue2),
                (values1, values2) -> {values1.putAll(values2); return values1;})
        ));

    printMap(map);
}

输出:

a: {2022_value2: 13, 2021_value1: 10, 2022_value1: 15, 2021_value2: 11}
b: {2022_value2: 37, 2021_value1: 20, 2022_value1: 30, 2021_value2: 25}

注意:

  • 如果考虑性能,Collector.of() 会稍好一些,因为它不会创建中间集合。
  • 为了使这种方法正常工作(我的意思是上面列出的代码以及问题中的代码),unitIdyear 的每个组合都应该是唯一的。否则,请考虑添加解决重复的逻辑。

关于Java Streams - 分组并返回嵌套映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72256255/

相关文章:

java - 我如何评估哈希表的实现? (引用HashMap)

java - EJB:自定义身份验证和授权

java - 使用 JasperReports 的最佳方法是什么?

sql - mysql 获取 order by 发生在 group by 之前

MySQL 选择子项的所有信息

java - 如何在 HashMap 中添加、删除和保存值

java Regex - 拆分但忽略引号内的文本?

java - Eclipse 中的 exec-maven-plugin :1. 2.1

php - 如何选择有多少用户具有不同的行值

java - mybatis中的hashmap结果图