!808 EasyStream.toTree移除对predicate.test的返回结果为null的支持

Merge pull request !808 from emptypoint/EasyStream-issue
This commit is contained in:
Looly 2022-09-21 13:52:45 +00:00 committed by Gitee
commit d8bcf027f7
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 339 additions and 252 deletions

View File

@ -5,10 +5,7 @@ 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 +368,131 @@ public class CollectorUtil {
public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> entryToMap() {
return toMap(Map.Entry::getKey, Map.Entry::getValue);
}
/**
* <p>将集合转换为树默认用 {@code parentId == null} 来判断树的根节点
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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 <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
* }</pre>
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final boolean isParallel) {
return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel);
}
/**
* <p>将集合转换为树默认用 {@code parentId == pidValue} 来判断树的根节点可以为null
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @param idGetter id的getter对应的lambda可以写作 {@code Student::getId}
* @param pIdGetter parentId的getter对应的lambda可以写作 {@code Student::getParentId}
* @param pidValue pid的值
* @param childrenSetter children的setter对应的lambda可以写作{ @code Student::setChildren}
* @param isParallel 是否并行去组装数据量特别大时使用
* @param <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
* }</pre>
* @author VampireAchao
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final R pidValue,
final BiConsumer<T, List<T>> 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 isParallel 是否并行处理
* @param <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
* @author VampireAchao
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate,
final boolean isParallel) {
final List<T> parents = new ArrayList<>();
return Collectors.collectingAndThen(groupingBy(pIdGetter,
new SimpleCollector<>(ArrayList::new,
(acc, e) -> {
if (parentPredicate.test(e)) {
parents.add(e);
}
if (idGetter.apply(e) != null) {
acc.add(e);
}
},
(left, right) -> {
left.addAll(right);
return left;
},
CH_ID)),
getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> parents, childrenSetter, isParallel));
}
/**
* toTree的内联函数
* 因为需要在当前传入数据里查找所以这是一个结束操作
*
* @param idGetter id的getter对应的lambda可以写作 {@code Student::getId}
* @param parentFactory 顶部数据工厂方法
* @param childrenSetter children的setter对应的lambda可以写作 {@code Student::setChildren}
* @param isParallel 是否并行处理
* @param <T> 此处是元素类型
* @param <R> 此处是id的泛型限制
* @return list 组装好的树
* @author VampireAchao
*/
private static <R extends Comparable<R>, T> Function<Map<R, List<T>>, List<T>> getChildrenFromMapByPidAndSet(
final Function<T, R> idGetter,
final Function<Map<R, List<T>>, List<T>> parentFactory,
final BiConsumer<T, List<T>> childrenSetter,
final boolean isParallel) {
return pIdValuesMap -> {
EasyStream.of(pIdValuesMap.values(), isParallel).flat(Function.identity())
.forEach(value -> {
final List<T> children = pIdValuesMap.get(idGetter.apply(value));
if (children != null) {
childrenSetter.accept(value, children);
}
});
return parentFactory.apply(pIdValuesMap);
};
}
}

View File

@ -1,12 +1,10 @@
package cn.hutool.core.stream;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.mutable.MutableObj;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.*;
@ -266,91 +264,6 @@ public class EasyStream<T> extends AbstractEnhancedWrappedStream<T, EasyStream<T
return new EasyStream<>(stream);
}
/**
* <p>将集合转换为树默认用 {@code parentId == null} 作为顶部内置一个小递归
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* toTree(Student::getId, Student::getParentId, Student::setChildren);
* }</pre>
*/
public <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter) {
final 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> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
*/
public <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate) {
Objects.requireNonNull(parentPredicate);
final List<T> list = toList();
final 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(
final Function<T, R> idGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Map<R, List<T>> pIdValuesMap,
final List<T> parents) {
Objects.requireNonNull(idGetter);
Objects.requireNonNull(childrenSetter);
Objects.requireNonNull(pIdValuesMap);
final MutableObj<Consumer<List<T>>> recursiveRef = new MutableObj<>();
final Consumer<List<T>> recursive = values -> EasyStream.of(values, isParallel()).forEach(value -> {
final List<T> children = pIdValuesMap.get(idGetter.apply(value));
childrenSetter.accept(value, children);
recursiveRef.get().accept(children);
});
recursiveRef.set(recursive);
recursive.accept(parents);
return parents;
}
/**
* 建造者
*

View File

@ -184,6 +184,55 @@ public interface TerminableWrappedStream<T, S extends TerminableWrappedStream<T,
return unwrap().collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier));
}
/**
* <p>将集合转换为树默认用 {@code parentId == null} 来判断树的根节点
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* toTree(Student::getId, Student::getParentId, Student::setChildren);
* }</pre>
* @author VampireAchao
*/
default <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, 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 <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
* @author VampireAchao
*/
default <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel()));
}
// endregion
// region ============ to zip ============

View File

@ -40,7 +40,7 @@ public class AbstractEnhancedWrappedStreamTest {
@Test
public void testToSet() {
final List<Integer> list = asList(1, 2, 3);
Set<String> toSet = wrap(list).map(String::valueOf).toSet();
final Set<String> toSet = wrap(list).map(String::valueOf).toSet();
Assert.assertEquals(new HashSet<>(asList("1", "2", "3")), toSet);
}
@ -636,7 +636,7 @@ public class AbstractEnhancedWrappedStreamTest {
List<String> zip = wrap(orders).zip(list, (e1, e2) -> e1 + "." + e2).toList();
Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip);
zip = wrap((Stream<? extends Object>) EasyStream.iterate(1, i -> i + 1)).limit(10).zip(list, (e1, e2) -> e1 + "." + e2).toList();
zip = this.wrap((Stream<Integer>)EasyStream.iterate(1, i -> i + 1)).limit(10).zip(list, (e1, e2) -> e1 + "." + e2).toList();
Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip);
}
@ -663,15 +663,15 @@ public class AbstractEnhancedWrappedStreamTest {
}
@SafeVarargs
private static <T> Wrapper<T> wrap(T... array) {
private final <T> Wrapper<T> wrap(final T... array) {
return new Wrapper<>(Stream.of(array));
}
private static <T> Wrapper<T> wrap(Iterable<T> iterable) {
private <T> Wrapper<T> wrap(final Iterable<T> iterable) {
return new Wrapper<>(StreamSupport.stream(iterable.spliterator(), false));
}
private static <T> Wrapper<T> wrap(Stream<T> stream) {
private <T> Wrapper<T> wrap(final Stream<T> stream) {
return new Wrapper<>(stream);
}
@ -683,12 +683,12 @@ public class AbstractEnhancedWrappedStreamTest {
* @param stream 包装的流对象
* @throws NullPointerException {@code unwrap}{@code null}时抛出
*/
protected Wrapper(Stream<T> stream) {
protected Wrapper(final Stream<T> stream) {
super(stream);
}
@Override
public Wrapper<T> wrap(Stream<T> source) {
public Wrapper<T> wrap(final Stream<T> source) {
return new Wrapper<>(source);
}

View File

@ -2,6 +2,7 @@ package cn.hutool.core.stream;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import lombok.Data;
import lombok.experimental.Tolerate;
import org.junit.Assert;
@ -476,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)
@ -540,7 +541,7 @@ public class EasyStreamTest {
private Long id;
private Long parentId;
private List<Student> children;
private Boolean matchParent = false;
private Boolean matchParent;
@Tolerate
public Student() {