我想使用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,则它将被视为具有键但值为空的单个对象。)
答案 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]
}