使用Java 8流将数据分组到地图中

时间:2019-07-18 20:11:42

标签: java java-8 java-stream

我想使用Java 8流API对数据进行分组。所有具有parent_id的行都应分组在一起。下面是示例文本文件。结果应该是一个映射,其中id将是Integer,值将是各自的分组行。例如,在以下情况下,结果将是3个条目的映射。键1对应2个值,键2对应无值,键3对应1个值。

id      name    parent_id
1       A       (null)
2       B       1
3       C       1
4       D       (null)
5       E       (null)
6       F       5

代码段为:

Map<String, List<FileVO>> map= list.stream()
        .collect(groupingBy(FileVO::getParentId, toList()));

输出可以像:{A,{B,C}}, {D,{}},{E,{F}}

简单的规则是:如果parentId不为null,则这些记录应分组为一个列表。并且此列表将被视为map中的值。它的键将是parentId,它是实际的id(列ID的值,并且不会为null。而parentId可以为null。如果一条记录的parentId为空,并且其他记录的parentId列中没有其ID,则它将被视为具有键但值为空的单个对象。)

2 个答案:

答案 0 :(得分:2)

我认为您无法一次完成。

Map<Integer, String> roots = list.stream()
            .filter(myObject -> myObject.getParentId() == null)
            .collect(Collectors.toMap(MyObject::getId, MyObject::getName));

输出的ID和名称均为父级

  

{1 = A,4 = D,5 = E}

Map<Integer, List<String>> groupByParentId = list.stream()
            .filter(myObject -> myObject.getParentId() != null)
            .collect(Collectors.groupingBy(MyObject::getParentId,
                    Collectors.mapping(MyObject::getName, toList())));

输出按parentId分组

  

{1 = [B,C],5 = [F]}

最后一步是:

roots.forEach((k,v)->map.put(v,groupByParentId.getOrDefault(k,new ArrayList<>())));

流版本的更新:复杂度为O(n ^ 2)

  list.stream()
            .filter(myObject -> myObject.getParentId() == null)
            .collect(Collectors.toMap(MyObject::getName, MyObject::getId))
            .forEach((k, v) -> map.put(k, list.stream()
            .filter(myObject -> myObject.getParentId() == v)
            .map(MyObject::getName)
            .collect(Collectors.toList())));

或者您也可以使用这样的非流方式:(个人更喜欢非流版本)

注意:这样,根就是Map<String,Integer> roots

String root = "";
for (MyObject myObject : list) {
    if (myObject.getParentId() == null) {
       root = myObject.getName();
       map.put(root, new ArrayList<>());
    }
    if (roots.get(root).equals(myObject.getParentId())){
      map.computeIfAbsent(root, k -> new ArrayList<>()).add(myObject.getName());
    }
}

答案 1 :(得分:0)

这是解决您问题的更复杂的方法:

在第一个groupingBy()中,如果可用,请使用parentId,否则请使用id

Map<Integer, List<FileVO>> result = list.stream()
        .collect(Collectors.groupingBy(f -> Optional.ofNullable(f.getParentId()).orElse(f.getId())));

这将创建属于同一组的文件组:

{
  1: [
       {id: 1, name: "A", parentId: null},
       {id: 2, name: "B", parentId: 1},
       {id: 3, name: "C", parentId: 1}
  ],
  4: [
       {id: 4, name: "D", parentId : null}
  ],
  5: [
       {id: 5, name: "E", parentId : null},
       {id: 6, name: "F", parentId : 5}
  ]
}

第二步,您将在每个组中找到父元素。如果可以确保每个列表中的第一个元素都是父元素(如您的示例中所示,则可以使用以下元素:

Map<String, List<String>> result = list.stream()
        .collect(Collectors.groupingBy(f -> Optional.ofNullable(f.getParentId()).orElse(f.getId())))
        .entrySet().stream()
        .collect(Collectors.groupingBy(e -> e.getValue().get(0).getName(),
                Collectors.flatMapping(e -> e.getValue().stream().skip(1).map(FileVO::getName), Collectors.toList())));

这将仅采用第一个元素(父元素)的名称,并映射除父元素本身之外的所有元素的名称。

如果不能确保您需要使用以下更通用的解决方案:

Map<String, List<String>> result = list.stream()
        .collect(Collectors.groupingBy(f -> Optional.ofNullable(f.getParentId()).orElse(f.getId())))
        .entrySet().stream()
        .collect(Collectors.groupingBy(
                e -> e.getValue().stream().filter(f -> f.getId() == e.getKey()).findAny().map(FileVO::getName).orElseThrow(),
                Collectors.flatMapping(
                        e -> e.getValue().stream().filter(f -> f.getId() != e.getKey()).map(FileVO::getName),
                        Collectors.toList())));

实际上是相同的,但是使用我们之前创建的地图条目的键来搜索父元素。

这两种解决方案都将为您的示例数据返回此值:

{
  A: [B, C],
  D: [],
  E: [F]
}