From 699217b4c387f2d02128f7c59edd3c219072ef12 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 22 Aug 2022 19:37:14 +0800 Subject: [PATCH 1/3] =?UTF-8?q?EasyStream=20=E5=AF=B9=E9=80=92=E5=BD=92?= =?UTF-8?q?=E6=A0=91=E7=BB=93=E6=9E=84=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/stream/EasyStream.java | 89 +++++++++++++ .../cn/hutool/core/stream/EasyStreamTest.java | 124 ++++++++++++++++++ 2 files changed, 213 insertions(+) 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 212196afa..e29974a60 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 @@ -1322,6 +1322,95 @@ public class EasyStream implements Stream, Iterable { 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 此处是id、parentId的泛型限制 + * @return list 组装好的树 + * eg: + * {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren) } + */ + public > List toTree(Function idGetter, + Function pIdGetter, + BiConsumer> childrenSetter) { + Map> 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 此处是id、parentId的泛型限制 + * @return list 组装好的树 + * eg: + * {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent) } + */ + + public > List toTree(Function idGetter, + Function pIdGetter, + BiConsumer> childrenSetter, + Predicate parentPredicate) { + List list = toList(); + List 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 此处是id的泛型限制 + * @return list 组装好的树 + */ + private > List getChildrenFromMapByPidAndSet(Function idGetter, + BiConsumer> childrenSetter, + Map> pIdValuesMap, + List parents) { + MutableObj>> recursiveRef = new MutableObj<>(); + Consumer> recursive = values -> EasyStream.of(values).forEach(value -> { + List 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 flatTree(Function> childrenGetter, BiConsumer> childrenSetter) { + MutableObj>> recursiveRef = new MutableObj<>(); + Function> recursive = e -> EasyStream.of(childrenGetter.apply(e)).flat(recursiveRef.get()).unshift(e); + recursiveRef.set(recursive); + return flat(recursive).peek(e -> childrenSetter.accept(e, null)); + } + /** * 通过给定分组依据进行分组 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 51f3262e3..fd0ed4c6e 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 @@ -3,13 +3,18 @@ package cn.hutool.core.stream; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.Tolerate; import org.junit.Assert; import org.junit.Test; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; /** @@ -427,4 +432,123 @@ public class EasyStreamTest { Assert.assertTrue(EasyStream.of(1).isNotEmpty()); } + @Test + public void testToTree() { + Consumer test = o -> { + List 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 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 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 children; + private Boolean matchParent = false; + + @Tolerate + public Student() { + // this is an accessible parameterless constructor. + } + } } From 4c8ed54241e46728bd56ead86062ec4f32be7a52 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 22 Aug 2022 22:57:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E6=B5=81=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e29974a60..0e74e3488 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 @@ -1384,7 +1384,7 @@ public class EasyStream implements Stream, Iterable { Map> pIdValuesMap, List parents) { MutableObj>> recursiveRef = new MutableObj<>(); - Consumer> recursive = values -> EasyStream.of(values).forEach(value -> { + Consumer> recursive = values -> EasyStream.of(values, isParallel()).forEach(value -> { List children = pIdValuesMap.get(idGetter.apply(value)); childrenSetter.accept(value, children); recursiveRef.get().accept(children); From 563f398625a750c60897e41980204b16ebc087a8 Mon Sep 17 00:00:00 2001 From: achao Date: Tue, 23 Aug 2022 14:20:56 +0800 Subject: [PATCH 3/3] test --- .../src/test/java/cn/hutool/core/stream/EasyStreamTest.java | 2 ++ 1 file changed, 2 insertions(+) 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 85fc1fc7e..098fc12b7 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 @@ -551,6 +551,8 @@ public class EasyStreamTest { // this is an accessible parameterless constructor. } } + + @Test public void testTransform() { final boolean result = EasyStream.of(1, 2, 3) .transform(EasyStream::toList)