mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
!766 EasyStream 对递归树结构的支持,提供结束操作toTree、中间操作flatTree函数
Merge pull request !766 from 阿超/v6-dev
This commit is contained in:
commit
ccb7b61b8c
@ -1364,6 +1364,95 @@ public class EasyStream<T> implements Stream<T>, Iterable<T> {
|
|||||||
return collect(CollectorUtil.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier));
|
return collect(CollectorUtil.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将集合转换为树,默认用 {@code parentId == null} 作为顶部,内置一个小递归
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren}
|
||||||
|
* @param <R> 此处是id、parentId的泛型限制
|
||||||
|
* @return list 组装好的树
|
||||||
|
* eg:
|
||||||
|
* {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren) }
|
||||||
|
*/
|
||||||
|
public <R extends Comparable<R>> List<T> toTree(Function<T, R> idGetter,
|
||||||
|
Function<T, R> pIdGetter,
|
||||||
|
BiConsumer<T, List<T>> childrenSetter) {
|
||||||
|
Map<R, List<T>> pIdValuesMap = group(pIdGetter);
|
||||||
|
return getChildrenFromMapByPidAndSet(idGetter, childrenSetter, pIdValuesMap, pIdValuesMap.get(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将集合转换为树,自定义树顶部的判断条件,内置一个小递归(没错,lambda可以写递归)
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren}
|
||||||
|
* @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> Objects.equals(s.getParentId(),0L) }
|
||||||
|
* @param <R> 此处是id、parentId的泛型限制
|
||||||
|
* @return list 组装好的树
|
||||||
|
* eg:
|
||||||
|
* {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent) }
|
||||||
|
*/
|
||||||
|
|
||||||
|
public <R extends Comparable<R>> List<T> toTree(Function<T, R> idGetter,
|
||||||
|
Function<T, R> pIdGetter,
|
||||||
|
BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
Predicate<T> parentPredicate) {
|
||||||
|
List<T> list = toList();
|
||||||
|
List<T> parents = EasyStream.of(list).filter(e ->
|
||||||
|
// 此处是为了适配 parentPredicate.test空指针 情况
|
||||||
|
// 因为Predicate.test的返回值是boolean,所以如果 e -> null 这种返回null的情况,会直接抛出NPE
|
||||||
|
Opt.ofTry(() -> parentPredicate.test(e)).filter(Boolean::booleanValue).isPresent())
|
||||||
|
.toList();
|
||||||
|
return getChildrenFromMapByPidAndSet(idGetter, childrenSetter, EasyStream.of(list).group(pIdGetter), parents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toTree的内联函数,内置一个小递归(没错,lambda可以写递归)
|
||||||
|
* 因为需要在当前传入数据里查找,所以这是一个结束操作
|
||||||
|
*
|
||||||
|
* @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId}
|
||||||
|
* @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren}
|
||||||
|
* @param pIdValuesMap parentId和值组成的map,用来降低复杂度
|
||||||
|
* @param parents 顶部数据
|
||||||
|
* @param <R> 此处是id的泛型限制
|
||||||
|
* @return list 组装好的树
|
||||||
|
*/
|
||||||
|
private <R extends Comparable<R>> List<T> getChildrenFromMapByPidAndSet(Function<T, R> idGetter,
|
||||||
|
BiConsumer<T, List<T>> childrenSetter,
|
||||||
|
Map<R, List<T>> pIdValuesMap,
|
||||||
|
List<T> parents) {
|
||||||
|
MutableObj<Consumer<List<T>>> recursiveRef = new MutableObj<>();
|
||||||
|
Consumer<List<T>> recursive = values -> EasyStream.of(values, isParallel()).forEach(value -> {
|
||||||
|
List<T> children = pIdValuesMap.get(idGetter.apply(value));
|
||||||
|
childrenSetter.accept(value, children);
|
||||||
|
recursiveRef.get().accept(children);
|
||||||
|
});
|
||||||
|
recursiveRef.set(recursive);
|
||||||
|
recursive.accept(parents);
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将树递归扁平化为集合,内置一个小递归(没错,lambda可以写递归)
|
||||||
|
* 这是一个无状态中间操作
|
||||||
|
*
|
||||||
|
* @param childrenGetter 获取子节点的lambda,可以写作 {@code Student::getChildren}
|
||||||
|
* @param childrenSetter 设置子节点的lambda,可以写作 {@code Student::setChildren}
|
||||||
|
* @return EasyStream 一个流
|
||||||
|
* eg:
|
||||||
|
* {@code List students = EasyStream.of(studentTree).flatTree(Student::getChildren, Student::setChildren).toList() }
|
||||||
|
*/
|
||||||
|
public EasyStream<T> flatTree(Function<T, List<T>> childrenGetter, BiConsumer<T, List<T>> childrenSetter) {
|
||||||
|
MutableObj<Function<T, EasyStream<T>>> recursiveRef = new MutableObj<>();
|
||||||
|
Function<T, EasyStream<T>> recursive = e -> EasyStream.of(childrenGetter.apply(e)).flat(recursiveRef.get()).unshift(e);
|
||||||
|
recursiveRef.set(recursive);
|
||||||
|
return flat(recursive).peek(e -> childrenSetter.accept(e, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过给定分组依据进行分组
|
* 通过给定分组依据进行分组
|
||||||
|
@ -3,13 +3,18 @@ package cn.hutool.core.stream;
|
|||||||
|
|
||||||
import cn.hutool.core.collection.ListUtil;
|
import cn.hutool.core.collection.ListUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Tolerate;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -427,6 +432,126 @@ public class EasyStreamTest {
|
|||||||
Assert.assertTrue(EasyStream.of(1).isNotEmpty());
|
Assert.assertTrue(EasyStream.of(1).isNotEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToTree() {
|
||||||
|
Consumer<Object> test = o -> {
|
||||||
|
List<Student> studentTree = EasyStream
|
||||||
|
.of(
|
||||||
|
Student.builder().id(1L).name("dromara").build(),
|
||||||
|
Student.builder().id(2L).name("baomidou").build(),
|
||||||
|
Student.builder().id(3L).name("hutool").parentId(1L).build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L).build(),
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(),
|
||||||
|
Student.builder().id(6L).name("looly").parentId(3L).build(),
|
||||||
|
Student.builder().id(7L).name("click33").parentId(4L).build(),
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
)
|
||||||
|
// just 3 lambda,top parentId is null
|
||||||
|
.toTree(Student::getId, Student::getParentId, Student::setChildren);
|
||||||
|
Assert.assertEquals(asList(
|
||||||
|
Student.builder().id(1L).name("dromara")
|
||||||
|
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build()))
|
||||||
|
.build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(2L).name("baomidou")
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L)
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
))
|
||||||
|
.build()))
|
||||||
|
.build()
|
||||||
|
), studentTree);
|
||||||
|
};
|
||||||
|
test = test.andThen(o -> {
|
||||||
|
List<Student> studentTree = EasyStream
|
||||||
|
.of(
|
||||||
|
Student.builder().id(1L).name("dromara").matchParent(true).build(),
|
||||||
|
Student.builder().id(2L).name("baomidou").matchParent(true).build(),
|
||||||
|
Student.builder().id(3L).name("hutool").parentId(1L).build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L).build(),
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(),
|
||||||
|
Student.builder().id(6L).name("looly").parentId(3L).build(),
|
||||||
|
Student.builder().id(7L).name("click33").parentId(4L).build(),
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
)
|
||||||
|
// just 4 lambda ,top by condition
|
||||||
|
.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
|
||||||
|
Assert.assertEquals(asList(
|
||||||
|
Student.builder().id(1L).name("dromara").matchParent(true)
|
||||||
|
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build()))
|
||||||
|
.build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(2L).name("baomidou").matchParent(true)
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L)
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
))
|
||||||
|
.build()))
|
||||||
|
.build()
|
||||||
|
), studentTree);
|
||||||
|
});
|
||||||
|
test.accept(new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlatTree() {
|
||||||
|
List<Student> studentTree = asList(
|
||||||
|
Student.builder().id(1L).name("dromara")
|
||||||
|
.children(asList(Student.builder().id(3L).name("hutool").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L)
|
||||||
|
.children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build()))
|
||||||
|
.build()))
|
||||||
|
.build(),
|
||||||
|
Student.builder().id(2L).name("baomidou")
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L)
|
||||||
|
.children(singletonList(
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
))
|
||||||
|
.build()))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(asList(
|
||||||
|
Student.builder().id(1L).name("dromara").build(),
|
||||||
|
Student.builder().id(2L).name("baomidou").build(),
|
||||||
|
Student.builder().id(3L).name("hutool").parentId(1L).build(),
|
||||||
|
Student.builder().id(4L).name("sa-token").parentId(1L).build(),
|
||||||
|
Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(),
|
||||||
|
Student.builder().id(6L).name("looly").parentId(3L).build(),
|
||||||
|
Student.builder().id(7L).name("click33").parentId(4L).build(),
|
||||||
|
Student.builder().id(8L).name("jobob").parentId(5L).build()
|
||||||
|
), EasyStream.of(studentTree).flatTree(Student::getChildren, Student::setChildren).sorted(Comparator.comparingLong(Student::getId)).toList());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public static class Student {
|
||||||
|
private String name;
|
||||||
|
private Integer age;
|
||||||
|
private Long id;
|
||||||
|
private Long parentId;
|
||||||
|
private List<Student> children;
|
||||||
|
private Boolean matchParent = false;
|
||||||
|
|
||||||
|
@Tolerate
|
||||||
|
public Student() {
|
||||||
|
// this is an accessible parameterless constructor.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTransform() {
|
public void testTransform() {
|
||||||
final boolean result = EasyStream.of(1, 2, 3)
|
final boolean result = EasyStream.of(1, 2, 3)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user