首页 > 解决方案 > java stream groupingBy + aggregate

问题描述

public class Member {
  private String name;
  private String team;
  private List<String> tasks;
  ...
}

input list like:

member1, team1, [task1]
member2, team1, [task2]
member2, team1, [taks3]
member3, team2, [task4]
member3, team2, [task5, task6]

Try to group by team and aggregate the tasks for each member together, how to write the stream? The expected result is Map<String, List<Member>> like:

team1: [
  member1: [task1]
  member2: [task2, task3]
]
team2: [
  member3: [task4, task5, task6]
]

grouping by team is easy. The challenge is how to aggregate the tasks for each different member and still maintain the result as `Map>

UPDATE: This is the best I can do, but it's 2 steps. Can anyone merge these to one?

public class Test {
public static void main(String[] args) {
    Member m1 = new Member("member1", "team1", task("task1"));
    Member m2 = new Member("member2", "team1", task("task2"));
    Member m3 = new Member("member2", "team1", task("task3"));
    Member m4 = new Member("member3", "team2", task("task4"));
    Member m5 = new Member("member3", "team2", task("task5"));

    List<Member> list = Arrays.asList(m1, m2, m3, m4, m5);

    long start = System.currentTimeMillis();

    Map<String, Map<String, Optional<Member>>> result = list.stream()
            .collect(Collectors.groupingBy(Member::getTeam,
                    Collectors.groupingBy(Member::getName, Collectors.reducing(
                            (member1, member2) -> {
                                member1.getTasks().addAll(member2.getTasks());
                                return member1;
                            }
                    ))));
    Map<String, List<Member>> result2 = result.entrySet().stream()
            .collect(Collectors.toMap(entry -> entry.getKey(),
                    entry -> entry.getValue().values().stream()
                            .map(e -> e.get()).collect(Collectors.toList())));

    result2.entrySet().stream().forEach(System.out::println);

    long end = System.currentTimeMillis();
    System.out.println("Total:" + (end - start) + "ms");
}

private static List<String> task(String... tasks) {
    List<String> list = new ArrayList<>();
    Collections.addAll(list, tasks);
    return list;
}

}

标签: javajava-8java-stream

解决方案


以下收集器组合应该做到这一点。

我确信这是一个很好的例子,说明了技术上的可能性,但在可读性方面并不是一个好主意。

List<Member> list = ...
Map<String, Map<String, List<String>>> map = list.stream()
        .collect(Collectors.groupingBy(Member::getTeam, 
            Collectors.groupingBy(Member::getName,
              Collectors.mapping(Member::getTasks,
                Collectors.collectingAndThen(Collectors.toList(), 
                    (doubleList) -> doubleList.stream()
                      .flatMap(lst -> lst.stream())
                        .collect(Collectors.toList()))))));

当使用以下输入(添加的构造函数)进行测试时:

List<Member> list = Arrays.asList(
        new Member("name1", "team1", Arrays.asList("task1", "task11")),
        new Member("name1", "team1", Arrays.asList("task2", "task22")),
        new Member("name1", "team2", Arrays.asList("task1", "task11")));

生成的地图包含:

{team1={name1=[task1, task11, task2, task22]}, team2={name1=[task1, task11]}}

推荐阅读