From 41ce120da56a05706c548b3f201209ddc9c46b7f Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 19 Sep 2022 11:16:27 +0800 Subject: [PATCH] =?UTF-8?q?:trollface:=20=E4=BC=98=E5=8C=96toTree=E6=80=A7?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=8A=BD=E5=8F=96=E5=88=B0CollectorUtil?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/stream/CollectorUtil.java | 133 +++++++++++++++++- .../cn/hutool/core/stream/EasyStream.java | 54 ++----- .../cn/hutool/core/stream/EasyStreamTest.java | 8 +- 3 files changed, 144 insertions(+), 51 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 14ffe6b6c..9b9e483dc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -1,14 +1,12 @@ package cn.hutool.core.stream; import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.mutable.MutableObj; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; +import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -371,4 +369,131 @@ public class CollectorUtil { public static Collector, ?, Map> entryToMap() { return toMap(Map.Entry::getKey, Map.Entry::getValue); } + + /** + *

将集合转换为树,默认用 {@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 isParallel 是否并行去组装,数据量特别大时使用 + * @param 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *

{@code
+	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
+	 * }
+ */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final boolean isParallel) { + return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel); + } + + /** + *

将集合转换为树,默认用 {@code parentId == pidValue} 来判断树的根节点,可以为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 isParallel 是否并行去组装,数据量特别大时使用 + * @param 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *

{@code
+	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
+	 * }
+ * @author VampireAchao + */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final R pidValue, + final BiConsumer> childrenSetter, + final boolean isParallel) { + return Collectors.collectingAndThen(groupingBy(pIdGetter, Collectors.toList()), + getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> pIdValuesMap.get(pidValue), childrenSetter, isParallel)); + } + + /** + * 将集合转换为树,自定义根节点的判断条件,内置一个递归,注意内存开销 + * 因为需要在当前传入数据里查找,所以这是一个结束操作 + * + * @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 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *
{@code
+	 * List studentTree = EasyStream.of(students).
+	 * 	.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
+	 * }
+ * @author VampireAchao + */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final Predicate parentPredicate, + boolean isParallel) { + List parents = new ArrayList<>(); + return Collectors.collectingAndThen(groupingBy(pIdGetter, + new SimpleCollector<>(ArrayList::new, + (acc, e) -> { + if (parentPredicate.test(e)) { + parents.add(e); + } + acc.add(e); + }, + (left, right) -> { + left.addAll(right); + return left; + }, + CH_ID)), + getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> parents, childrenSetter, isParallel)); + } + + /** + * toTree的内联函数,内置一个小递归(没错,lambda可以写递归) + * 因为需要在当前传入数据里查找,所以这是一个结束操作 + * + * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} + * @param parentFactory 顶部数据工厂方法 + * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} + * @param isParallel 是否并行处理 + * @param 此处是元素类型 + * @param 此处是id的泛型限制 + * @return list 组装好的树 + * @author VampireAchao + */ + private static , T> Function>, List> getChildrenFromMapByPidAndSet( + final Function idGetter, + final Function>, List> parentFactory, + final BiConsumer> childrenSetter, + boolean isParallel) { + return pIdValuesMap -> { + final MutableObj>> recursiveRef = new MutableObj<>(); + final Consumer> recursive = parents -> EasyStream.of(parents, isParallel).forEach(parent -> { + final List children = pIdValuesMap.get(idGetter.apply(parent)); + childrenSetter.accept(parent, children); + recursiveRef.get().accept(children); + }); + List parents = parentFactory.apply(pIdValuesMap); + if (!parents.isEmpty()) { + recursiveRef.set(recursive); + recursiveRef.get().accept(parents); + } + return parents; + }; + } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java index f2af24e8b..9b77b1cf5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java @@ -4,9 +4,7 @@ import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Spliterator; import java.util.function.*; @@ -267,7 +265,7 @@ public class EasyStream extends AbstractEnhancedWrappedStream将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点 + *

将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点,内置一个递归,注意内存开销 * 因为需要在当前传入数据里查找,所以这是一个结束操作
* * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} @@ -280,18 +278,17 @@ public class EasyStream extends AbstractEnhancedWrappedStream studentTree = EasyStream.of(students). * toTree(Student::getId, Student::getParentId, Student::setChildren); * } + * @author VampireAchao */ public > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter) { - // 使用 parentId == null 判断是否为根节点 - final Predicate parentPredicate = node -> null == pIdGetter.apply(node); - return toTree(idGetter, pIdGetter, childrenSetter, parentPredicate); + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter) { + return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel())); } /** - * 将集合转换为树,自定义根节点的判断条件 + * 将集合转换为树,自定义根节点的判断条件,内置一个递归,注意内存开销 * 因为需要在当前传入数据里查找,所以这是一个结束操作 * * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} @@ -305,39 +302,14 @@ public class EasyStream extends AbstractEnhancedWrappedStream studentTree = EasyStream.of(students). * .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent); * } + * @author VampireAchao */ public > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final Predicate parentPredicate) { - Objects.requireNonNull(idGetter); - Objects.requireNonNull(pIdGetter); - Objects.requireNonNull(childrenSetter); - Objects.requireNonNull(parentPredicate); - - List nodeList = toList(); - // 根据 父id 分组,让key为null的组中全是根节点 - final Function pIdClassifier = node -> { - // 该节点是根节点, 分到 父id 为null的组中 - if (parentPredicate.test(node)) { - return null; - } - // 返回 父id - return pIdGetter.apply(node); - }; - // 父id 关联的 子节点列表 - final Map> pId2ChildrenMap = of(nodeList).group(pIdClassifier); - - of(nodeList, true).forEach(node -> { - // 设置 该节点的子节点列表 - final List children = pId2ChildrenMap.get(idGetter.apply(node)); - if (children != null) { - childrenSetter.accept(node, children); - } - }); - // 返回根节点列表 - return pId2ChildrenMap.getOrDefault(null, Collections.emptyList()); + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final Predicate parentPredicate) { + return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel())); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java index 9ea222491..166a3f201 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java @@ -182,7 +182,7 @@ public class EasyStreamTest { Assert.assertEquals(collect2, distinctBy2); Assert.assertEquals( - 4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count() + 4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count() ); } @@ -477,7 +477,7 @@ public class EasyStreamTest { Student.builder().id(8L).name("jobob").parentId(5L).build() ) // just 4 lambda ,top by condition - .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent); + .toTree(Student::getId, Student::getParentId, Student::setChildren, s -> BooleanUtil.isTrue(s.getMatchParent())); Assert.assertEquals(asList( Student.builder().id(1L).name("dromara").matchParent(true) .children(asList(Student.builder().id(3L).name("hutool").parentId(1L) @@ -547,10 +547,6 @@ public class EasyStreamTest { public Student() { // this is an accessible parameterless constructor. } - - public Boolean getMatchParent() { - return BooleanUtil.isTrue(matchParent); - } } @Test