refactor(hierarchy): 移除 HierarchyUtil 相关组件,改为使用更精简的 HierarchyIterator 实现

This commit is contained in:
huangchengxing 2023-12-28 20:26:29 +08:00
parent 3f8ee8a429
commit 5e03f627b3
13 changed files with 567 additions and 590 deletions

View File

@ -21,11 +21,10 @@ import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.io.resource.ResourceUtil;
import org.dromara.hutool.core.net.url.UrlDecoder;
import org.dromara.hutool.core.net.url.UrlUtil;
import org.dromara.hutool.core.stream.EasyStream;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.split.SplitUtil;
import org.dromara.hutool.core.tree.hierarchy.HierarchyIteratorUtil;
import org.dromara.hutool.core.tree.hierarchy.HierarchyUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import java.io.IOException;
@ -826,10 +825,9 @@ public class ClassUtil {
*/
public static void traverseTypeHierarchyWhile(
final Class<?> root, final Predicate<Class<?>> filter, final Predicate<Class<?>> terminator) {
HierarchyUtil.traverseByBreadthFirst(
root, filter,
HierarchyIteratorUtil.scan(ClassUtil::getNextTypeHierarchies, terminator.negate())
);
EasyStream.iterateHierarchies(root, ClassUtil::getNextTypeHierarchies, filter)
.takeWhile(terminator)
.exec();
}
/**
@ -857,7 +855,7 @@ public class ClassUtil {
}
return getNextTypeHierarchies(t);
};
HierarchyUtil.traverseByBreadthFirst(root, filter, HierarchyIteratorUtil.scan(function));
EasyStream.iterateHierarchies(root, function, filter).exec();
}
private static Set<Class<?>> getNextTypeHierarchies(final Class<?> t) {

View File

@ -12,14 +12,15 @@
package org.dromara.hutool.core.stream;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.lang.Opt;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.util.ObjUtil;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.OptionalDouble;
@ -223,6 +224,55 @@ public class EasyStream<T> extends AbstractEnhancedWrappedStream<T, EasyStream<T
return new EasyStream<>(StreamUtil.iterate(seed, hasNext, next));
}
/**
* <p>指定一个层级结构的根节点通常是树或图
* 然后获取包含根节点在内根节点所有层级结构中的节点组成的流<br />
* 该方法用于以平铺的方式对图或树节点进行访问可以使用并行流提高效率
*
* <p>eg:
* <pre>{@code
* Tree root = // 构建树结构
* // 搜索树结构中所有级别为3的节点并按权重排序
* List<Tree> thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
* .filter(node -> node.getLevel() == 3)
* .sorted(Comparator.comparing(Tree::getWeight))
* .toList();
* }</pre>
*
* @param root 根节点
* @param discoverer 下一层级节点的获取方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
* @return 包含根节点在内根节点所有层级结构中的节点组成的流
*/
public static <T> EasyStream<T> iterateHierarchies(
T root, final Function<T, Collection<T>> discoverer, Predicate<T> filter) {
return of(StreamUtil.iterateHierarchies(root, discoverer, filter));
}
/**
* <p>指定一个层级结构的根节点通常是树或图
* 然后获取包含根节点在内根节点所有层级结构中的节点组成的流<br />
* 该方法用于以平铺的方式对图或树节点进行访问可以使用并行流提高效率
*
* <p>eg:
* <pre>{@code
* Tree root = // 构建树结构
* // 搜索树结构中所有级别为3的节点并按权重排序
* List<Tree> thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
* .filter(node -> node.getLevel() == 3)
* .sorted(Comparator.comparing(Tree::getWeight))
* .toList();
* }</pre>
*
* @param root 根节点
* @param discoverer 下一层级节点的获取方法
* @return 包含根节点在内根节点所有层级结构中的节点组成的流
*/
public static <T> EasyStream<T> iterateHierarchies(
T root, final Function<T, Collection<T>> discoverer) {
return of(StreamUtil.iterateHierarchies(root, discoverer));
}
/**
* 返回无限串行无序流
* 其中每一个元素都由给定的{@link Supplier}生成

View File

@ -12,11 +12,13 @@
package org.dromara.hutool.core.stream;
import static java.util.Objects.requireNonNull;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.stream.spliterators.DropWhileSpliterator;
import org.dromara.hutool.core.stream.spliterators.IterateSpliterator;
import org.dromara.hutool.core.stream.spliterators.TakeWhileSpliterator;
import org.dromara.hutool.core.tree.HierarchyIterator;
import org.dromara.hutool.core.util.CharsetUtil;
import java.io.File;
@ -34,8 +36,6 @@ import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;
/**
* {@link Stream} 工具类
*
@ -210,6 +210,59 @@ public class StreamUtil {
return StreamSupport.stream(IterateSpliterator.create(seed, hasNext, next), false);
}
/**
* <p>指定一个层级结构的根节点通常是树或图
* 然后获取包含根节点在内根节点所有层级结构中的节点组成的流<br />
* 该方法用于以平铺的方式按广度优先对图或树节点进行访问可以使用并行流提高效率
*
* <p>eg:
* <pre>{@code
* Tree root = // 构建树结构
* // 搜索树结构中所有级别为3的节点并按权重排序
* List<Tree> thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
* .filter(node -> node.getLevel() == 3)
* .sorted(Comparator.comparing(Tree::getWeight))
* .toList();
* }</pre>
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param discoverer 下一层级节点的获取方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
* @return 包含根节点在内根节点所有层级结构中的节点组成的流
* @param <T> 元素类型
* @see HierarchyIterator
*/
public static <T> Stream<T> iterateHierarchies(
T root, final Function<T, Collection<T>> discoverer, Predicate<T> filter) {
return ofIter(HierarchyIterator.breadthFirst(root, discoverer, filter));
}
/**
* <p>指定一个层级结构的根节点通常是树或图
* 然后获取包含根节点在内根节点所有层级结构中的节点组成的流<br />
* 该方法用于以平铺的方式按广度优先对图或树节点进行访问可以使用并行流提高效率
*
* <p>eg:
* <pre>{@code
* Tree root = // 构建树结构
* // 搜索树结构中所有级别为3的节点并按权重排序
* List<Tree> thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
* .filter(node -> node.getLevel() == 3)
* .sorted(Comparator.comparing(Tree::getWeight))
* .toList();
* }</pre>
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param discoverer 下一层级节点的获取方法
* @return 包含根节点在内根节点所有层级结构中的节点组成的流
* @param <T> 元素类型
* @see HierarchyIterator
*/
public static <T> Stream<T> iterateHierarchies(
T root, final Function<T, Collection<T>> discoverer) {
return ofIter(HierarchyIterator.breadthFirst(root, discoverer));
}
/**
* 保留 与指定断言 匹配时的元素, 在第一次不匹配时终止, 抛弃当前(第一个不匹配元素)及后续所有元素
* <p> jdk9 中的 takeWhile 方法不太一样, 这里的实现是个 顺序的有状态的中间操作</p>

View File

@ -0,0 +1,238 @@
package org.dromara.hutool.core.tree;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.stream.EasyStream;
import org.dromara.hutool.core.stream.StreamUtil;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* <p>用于迭代层级结构比如树或图的迭代器
* 支持{@link #depthFirst 广度优先}{@link #breadthFirst 深度优先}两种遍历模式<br />
* 迭代器仅适用于访问层级结构因此不支持{@link Iterator#remove}方法
* 要构建树或者操作数请参见{@link BeanTree}{@link TreeUtil}
*
* <p>该迭代器侧重于打通图或树这类数据结构与传统集合间的隔阂
* 从而支持通过传统可迭代集合的方式对树或图中的节点进行操作<br />
* 比如
* <pre>{@code
* Tree root = // 构建树结构
* // 搜索树结构中所有级别为3的节点并按权重排序
* List<Tree> thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
* .filter(node -> node.getLevel() == 3)
* .sorted(Comparator.comparing(Tree::getWeight))
* .toList();
* }</pre>
*
* @author huangchengxing
* @param <T> 元素类型
* @see EasyStream#iterateHierarchies
* @see StreamUtil#iterateHierarchies
*/
public abstract class HierarchyIterator<T> implements Iterator<T> {
/**
* 下一层级节点的获取方法
*/
protected final Function<T, Collection<T>> elementDiscoverer;
/**
* 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
*/
protected final Predicate<T> filter;
/**
* 已经访问过的列表
*/
protected final Set<T> accessed = new HashSet<>();
/**
* 当前待遍历的节点
*/
protected final LinkedList<T> queue = new LinkedList<>();
/**
* 获取一个迭代器用于按广度优先迭代层级结构中的每一个结点
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 下一层级节点的获取方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
* @param <T> 元素类型
* @return 迭代器
*/
public static <T> HierarchyIterator<T> breadthFirst(
final T root, final Function<T, Collection<T>> nextDiscoverer, final Predicate<T> filter) {
return new BreadthFirst<>(root, nextDiscoverer, filter);
}
/**
* 获取一个迭代器用于按广度优先迭代层级结构中的每一个结点
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 下一层级节点的获取方法
* @param <T> 元素类型
* @return 迭代器
*/
public static <T> HierarchyIterator<T> breadthFirst(
final T root, final Function<T, Collection<T>> nextDiscoverer) {
return breadthFirst(root, nextDiscoverer, t -> true);
}
/**
* 获取一个迭代器用于按深度优先迭代层级结构中的每一个结点
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 下一层级节点的获取方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
* @param <T> 元素类型
* @return 迭代器
*/
public static <T> HierarchyIterator<T> depthFirst(
final T root, final Function<T, Collection<T>> nextDiscoverer, final Predicate<T> filter) {
return new DepthFirst<>(root, nextDiscoverer, filter);
}
/**
* 获取一个迭代器用于按深度优先迭代层级结构中的每一个结点
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 下一层级节点的获取方法
* @param <T> 元素类型
* @return 迭代器
*/
public static <T> HierarchyIterator<T> depthFirst(
final T root, final Function<T, Collection<T>> nextDiscoverer) {
return depthFirst(root, nextDiscoverer, t -> true);
}
/**
* 创建一个迭代器
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param elementDiscoverer 获取下一层级节点的方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
*/
HierarchyIterator(final T root, final Function<T, Collection<T>> elementDiscoverer, final Predicate<T> filter) {
// 根节点不允许被过滤
Assert.isTrue(filter.test(root), "root node cannot be filtered!");
queue.add(root);
this.elementDiscoverer = Assert.notNull(elementDiscoverer);
this.filter = Assert.notNull(filter);
}
/**
* 是否仍有下一个节点
*
* @return 是否
*/
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
/**
* 获取下一个节点
*
* @return 下一个节点
*/
@Override
public T next() {
if (queue.isEmpty()) {
throw new NoSuchElementException();
}
final T curr = queue.removeFirst();
accessed.add(curr);
Collection<T> nextElements = elementDiscoverer.apply(curr);
if (Objects.nonNull(nextElements) && !nextElements.isEmpty()) {
nextElements = nextElements.stream()
.filter(filter)
.collect(Collectors.toList());
collectNextElementsToQueue(nextElements);
}
return curr;
}
/**
* 将下一层级的节点搜集到队列
*
* @param nextElements 待收集的下一层级的节点
* @see #queue
* @see #accessed
*/
protected abstract void collectNextElementsToQueue(final Collection<T> nextElements);
/**
* 深度优先遍历
*
* @param <T> 元素
*/
static class DepthFirst<T> extends HierarchyIterator<T> {
/**
* 创建一个迭代器
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 获取下一层级节点的方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
*/
DepthFirst(final T root, final Function<T, Collection<T>> nextDiscoverer, final Predicate<T> filter) {
super(root, nextDiscoverer, filter);
}
/**
* 将下一层级的节点搜集到队列
*
* @param nextElements 待收集的下一层级的节点
*/
@Override
protected void collectNextElementsToQueue(final Collection<T> nextElements) {
int idx = 0;
for (final T nextElement : nextElements) {
if (!accessed.contains(nextElement)) {
queue.add(idx++, nextElement);
accessed.add(nextElement);
}
}
}
}
/**
* 广度优先遍历
*
* @param <T> 元素类型
*/
static class BreadthFirst<T> extends HierarchyIterator<T> {
/**
* 创建一个迭代器
*
* @param root 根节点根节点不允许被{@code filter}过滤
* @param nextDiscoverer 获取下一层级节点的方法
* @param filter 节点过滤器不匹配的节点与以其作为根节点的子树将将会被忽略
*/
BreadthFirst(final T root, final Function<T, Collection<T>> nextDiscoverer, final Predicate<T> filter) {
super(root, nextDiscoverer, filter);
}
/**
* 将下一层级的节点搜集到队列
*
* @param nextElements 待收集的下一层级的节点
*/
@Override
protected void collectNextElementsToQueue(final Collection<T> nextElements) {
for (final T nextElement : nextElements) {
if (!accessed.contains(nextElement)) {
queue.addLast(nextElement);
accessed.add(nextElement);
}
}
}
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.tree.hierarchy;
import java.util.Collection;
/**
* 用于遍历层级结构的迭代器提供了遍历过程中的回调方法
* <ul>
* <li>{@link #isBreak}: 是否中断遍历其在{@link #nextHierarchies}之前调用</li>
* <li>{@link #nextHierarchies}: 获得下一需要遍历的层级当为空时结束</li>
* <li>{@link #getResult}: 当遍历完成后获取迭代器结果</li>
* </ul>
*
* <p>默认提供了三类{@code HierarchyIterator}实现
* <ul>
* <li>scan: 遍历所有的层级结构</li>
* <li>collector: 收集层级结构中所有符合条件的结果并在结束遍历后返回所有结果</li>
* <li>find: 找到层级结构中符合条件的结果后立刻中断遍历并返回该结果</li>
* </ul>
* 可以实现自定义的{@code HierarchyIterator}来支持更多的遍历模式
*
* @param <H> 层级类型
* @param <R> 结果类型
* @author huangchengxing
* @since 6.0.0
*/
@FunctionalInterface
public interface HierarchyIterator<H, R> {
/**
* 获取下一层级
*
* @param result 结果
* @param hierarchy 当前层级
* @return 下一需要遍历的层级
*/
Collection<H> nextHierarchies(R result, H hierarchy);
/**
* 是否中断遍历
*
* @param hierarchy 当前层级
* @return 是否中断遍历
*/
default boolean isBreak(final H hierarchy) {
return false;
}
/**
* 获取结果
*
* @return 结果
*/
default R getResult() {
return null;
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.tree.hierarchy;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* {@code HierarchyIterator}的基本实现
*
* @param <H> 层级类型
* @param <R> 结果类型
* @author huangchengxing
*/
public class HierarchyIteratorImpl<H, R> implements HierarchyIterator<H, R> {
private final Supplier<? extends R> resultFactory;
private final Predicate<? super H> hierarchyFilter;
private final BiFunction<? super R, ? super H, ? extends Collection<H>> hierarchyFinder;
/**
* 构造
*
* @param resultFactory 结果创建类
* @param hierarchyFilter 层级过滤器
* @param hierarchyFinder 层级查找器
*/
public HierarchyIteratorImpl(
final Supplier<? extends R> resultFactory, final Predicate<? super H> hierarchyFilter,
final BiFunction<? super R, ? super H, ? extends Collection<H>> hierarchyFinder) {
this.resultFactory = resultFactory;
this.hierarchyFilter = hierarchyFilter;
this.hierarchyFinder = hierarchyFinder;
}
/**
* 获取下一层级
*
* @param result 结果
* @param hierarchy 当前层级
* @return 下一需要遍历的层级
*/
@Override
public Collection<H> nextHierarchies(final R result, final H hierarchy) {
return hierarchyFinder.apply(result, hierarchy);
}
/**
* 是否中断遍历
*
* @param hierarchy 当前层级
* @return 是否中断遍历
*/
@Override
public boolean isBreak(final H hierarchy) {
return hierarchyFilter.test(hierarchy);
}
/**
* 获取结果
*
* @return 结果
*/
@Override
public R getResult() {
return resultFactory.get();
}
}

View File

@ -1,133 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.tree.hierarchy;
import org.dromara.hutool.core.lang.mutable.Mutable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* {@link HierarchyIterator}创建工具类
*
* @author huangchengxing
* @since 6.0.0
*/
public class HierarchyIteratorUtil {
/**
* 创建一个{@code HierarchyIterator}对象, {@code finder}返回非空时, 迭代器立刻中断, 返回结果
*
* @param function 迭代器处理函数
* @param finder 查找器
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return {@code HierarchyIterator}
*/
public static <H, R> HierarchyIterator<H, R> find(
final Function<H, Collection<H>> function, final Function<H, R> finder) {
Objects.requireNonNull(function);
Objects.requireNonNull(finder);
final Mutable<R> mutable = Mutable.of(null);
final Predicate<H> terminator = h -> {
final R r = finder.apply(h);
if (r != null) {
mutable.set(r);
return true;
}
return false;
};
return new HierarchyIteratorImpl<>(mutable::get, terminator, (r, h) -> function.apply(h));
}
/**
* 创建一个{@code HierarchyIterator}对象, 迭代器结果总是为{@code null}
*
* @param function 迭代器处理函数
* @param terminator 是否终止遍历
* @param <H> 层级结构类型
* @return {@code HierarchyIterator}
*/
public static <H> HierarchyIterator<H, Void> scan(
final Function<H, Collection<H>> function, final Predicate<H> terminator) {
Objects.requireNonNull(function);
return new HierarchyIteratorImpl<>(() -> null, terminator, (r, h) -> function.apply(h));
}
/**
* 创建一个{@code HierarchyIterator}对象, 迭代器结果总是为{@code null}
*
* @param function 迭代器处理函数
* @param <H> 层级结构类型
* @return {@code HierarchyIterator}
*/
public static <H> HierarchyIterator<H, Void> scan(final Function<H, Collection<H>> function) {
return scan(function, h -> false);
}
/**
* 创建一个{@code HierarchyIterator}对象, {@code mapper}返回非空, 则将结果添加到集合中最终返回集合
*
* @param function 迭代器处理函数
* @param collFactory 集合工厂
* @param mapper 迭代器结果映射函数
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @param <C> 集合类型
* @return {@code HierarchyIterator}
*/
public static <H, R, C extends Collection<R>> HierarchyIterator<H, C> collect(
final Function<H, Collection<H>> function, final Supplier<C> collFactory, final Function<H, R> mapper) {
Objects.requireNonNull(function);
Objects.requireNonNull(collFactory);
Objects.requireNonNull(mapper);
final C collection = collFactory.get();
return new HierarchyIteratorImpl<>(() -> collection, h -> false, (r, h) -> {
final R apply = mapper.apply(h);
if (Objects.nonNull(apply)) {
collection.add(apply);
}
return function.apply(h);
});
}
/**
* 创建一个{@code HierarchyIterator}对象, {@code mapper}返回非空, 则将结果添加到集合中最终返回集合
*
* @param function 迭代器处理函数
* @param mapper 迭代器结果映射函数
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return {@code HierarchyIterator}
*/
public static <H, R> HierarchyIterator<H, List<R>> collect(
final Function<H, Collection<H>> function, final Function<H, R> mapper) {
return collect(function, ArrayList::new, mapper);
}
/**
* 创建一个{@code HierarchyIterator}对象, 则将非空结果添加到集合中最终返回集合
*
* @param function 迭代器处理函数
* @param <H> 层级结构类型
* @return {@code HierarchyIterator}
*/
public static <H> HierarchyIterator<H, List<H>> collect(
final Function<H, Collection<H>> function) {
return collect(function, Function.identity());
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.tree.hierarchy;
import org.dromara.hutool.core.collection.CollUtil;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* <p>通用层级结构扫描器工具类<br>
*
* <p>用于根据广度优先或深度优先或搜索遍历如树或图等特定数据结构的层级文件夹层级类层级方法或注解层级等
* 使用时指定遍历的起始节点以及遍历算法并配合{@link HierarchyIterator}即可完成遍历中的业务逻辑处理
*
* <p>比如我们可以使用其来访问树结构
* <pre>{@code
* // 构建一个树形结构
* Node root = Tree.build();
* // 从树结构中通过深度优先查找某个节点
* Node target = HierarchyUtil.traverseByDepthFirst(
* root, HierarchyIteratorUtil.find(Node::getChildren, node -> Objects.equals(node.getId(), "target") ? node : null)
* );
* // 从树结构中通过广度优先获取其所有的子节点
* List<Node> nodes = HierarchyUtil.traverseByBreadthFirst(
* root, HierarchyIteratorUtil.collect(Node::getChildren)
* );
* }</pre>
*
* @author huangchengxing
* @see HierarchyIterator
* @since 6.0.0
*/
public class HierarchyUtil {
// ================ breadth first ================
/**
* 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构
*
* @param hierarchy 层级结构
* @param filter 过滤器校验不通过的层级结构不会被查找
* @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return 迭代器结果
*/
public static <H, R> R traverseByBreadthFirst(
final H hierarchy, final Predicate<? super H> filter, final HierarchyIterator<H, R> hierarchyIterator) {
return scanHierarchies(hierarchy, filter, hierarchyIterator, Collection::addAll);
}
/**
* 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构
*
* @param hierarchy 层级结构
* @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return 迭代器结果
*/
public static <H, R> R traverseByBreadthFirst(final H hierarchy, final HierarchyIterator<H, R> hierarchyIterator) {
return traverseByBreadthFirst(hierarchy, h -> true, hierarchyIterator);
}
// ================ depth first ================
/**
* 按深度优先遍历指定包括{@code hierarchy}本身在内层级结构
*
* @param hierarchy 层级结构
* @param filter 过滤器校验不通过的层级结构不会被查找
* @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return 迭代器结果
*/
public static <H, R> R traverseByDepthFirst(
final H hierarchy, final Predicate<? super H> filter, final HierarchyIterator<H, R> hierarchyIterator) {
return scanHierarchies(hierarchy, filter, hierarchyIterator, (queue, hierarchies) -> {
// 跳跃式添加保证顺序
int index = 0;
for (final H h : hierarchies) {
queue.add(index++, h);
}
});
}
/**
* 按深度优先遍历指定包括{@code hierarchy}本身在内层级结构
*
* @param hierarchy 层级结构
* @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级
* @param <H> 层级结构类型
* @param <R> 迭代器结果类型
* @return 迭代器结果
*/
public static <H, R> R traverseByDepthFirst(final H hierarchy, final HierarchyIterator<H, R> hierarchyIterator) {
return traverseByDepthFirst(hierarchy, h -> true, hierarchyIterator);
}
private static <H, R> R scanHierarchies(
final H hierarchy, final Predicate<? super H> filter, final HierarchyIterator<H, R> hierarchyIterator,
final BiConsumer<List<H>, Collection<? extends H>> appender) {
Objects.requireNonNull(hierarchy);
Objects.requireNonNull(filter);
Objects.requireNonNull(hierarchyIterator);
// 遍历层级结构
final LinkedList<H> queue = new LinkedList<>();
final Set<H> accessed = new HashSet<>();
queue.add(hierarchy);
while (!queue.isEmpty()) {
// 跳过已经访问过或者被过滤的层级结构
final H curr = queue.removeFirst();
if (!accessed.add(curr) || !filter.test(curr)) {
continue;
}
// 若迭代器返回true则结束遍历
if (hierarchyIterator.isBreak(curr)) {
return hierarchyIterator.getResult();
}
// 获取下一需要遍历的层级
final Collection<? extends H> nextHierarchies = hierarchyIterator.nextHierarchies(hierarchyIterator.getResult(), curr);
if (CollUtil.isEmpty(nextHierarchies)) {
continue;
}
appender.accept(queue, nextHierarchies);
}
return hierarchyIterator.getResult();
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
* 用于处理层级结构的通用工具列结构类似 Stream + Collector<br>
* HierarchyUtil 提供深度优先和广度优先算法实现HierarchyIterator 则提供具体的操作逻辑
*
* @author huangchengxing
*/
package org.dromara.hutool.core.tree.hierarchy;

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.core.stream;
import static java.util.Collections.singletonList;
import lombok.Builder;
import lombok.Data;
import org.dromara.hutool.core.collection.ListUtil;
@ -28,13 +29,39 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.singletonList;
/**
* @author VampireAchao
*/
public class EasyStreamTest {
@Test
void testIterateHierarchies() {
// 创建一个三层的树结构每个节点都有两个子节点
Node node = new Node("1", Arrays.asList(
new Node("1-1", Arrays.asList(
new Node("1-1-1", null),
new Node("1-1-2", null)
)),
new Node("1-2", Arrays.asList(
new Node("1-2-1", null),
new Node("1-2-2", null)
))
));
// 按广度度优先遍历树结构
List<String> allNodes = new ArrayList<>();
EasyStream.iterateHierarchies(node, node1 -> node1.children)
.forEach(node1 -> allNodes.add(node1.id));
Assertions.assertEquals(Arrays.asList("1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"), allNodes);
// 按广度度优先遍历树结构忽略id为1-1的节点与以其为根节点的子树
List<String> filteredNodes = new ArrayList<>();
EasyStream.iterateHierarchies(node, node1 -> node1.children, node1 -> !node1.id.equals("1-1"))
.forEach(node1 -> filteredNodes.add(node1.id));
Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), filteredNodes);
}
@Test
public void testConcat() {
final Stream<Integer> stream1 = Stream.of(1, 2);
@ -568,4 +595,19 @@ public class EasyStreamTest {
private String name;
private BigDecimal count;
}
/**
* 节点
*/
private static class Node {
private final String id;
private List<Node> children;
private Node(String id, List<Node> children) {
this.id = id;
this.children = children;
}
public Node(String id) {
this.id = id;
}
}
}

View File

@ -17,12 +17,44 @@ import org.dromara.hutool.core.collection.set.SetUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamUtilTest {
@Test
void testIterateHierarchies() {
// 创建一个三层的树结构每个节点都有两个子节点
Node node = new Node("1", Arrays.asList(
new Node("1-1", Arrays.asList(
new Node("1-1-1", null),
new Node("1-1-2", null)
)),
new Node("1-2", Arrays.asList(
new Node("1-2-1", null),
new Node("1-2-2", null)
))
));
// 按广度度优先遍历树结构
List<String> allNodes = new ArrayList<>();
StreamUtil.iterateHierarchies(node, node1 -> node1.children)
.forEach(node1 -> allNodes.add(node1.id));
Assertions.assertEquals(Arrays.asList("1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"), allNodes);
// 按广度度优先遍历树结构忽略id为1-1的节点与以其为根节点的子树
List<String> filteredNodes = new ArrayList<>();
StreamUtil.iterateHierarchies(node, node1 -> node1.children, node1 -> !node1.id.equals("1-1"))
.forEach(node1 -> filteredNodes.add(node1.id));
Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), filteredNodes);
}
@Test
public void ofTest(){
final Stream<Integer> stream = StreamUtil.of(2, x -> x * 2, 4);
@ -61,4 +93,19 @@ public class StreamUtilTest {
Assertions.assertEquals(0, stream.toArray().length);
}
// ================ stream test end ================
/**
* 节点
*/
private static class Node {
private final String id;
private List<Node> children;
private Node(String id, List<Node> children) {
this.id = id;
this.children = children;
}
public Node(String id) {
this.id = id;
}
}
}

View File

@ -0,0 +1,126 @@
package org.dromara.hutool.core.tree;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
/**
* test for {@link HierarchyIterator}
*
* @author huangchengxing
*/
class HierarchyIteratorTest {
@Test
void testNoSuchElementException() {
Node node = createTree();
// 根节点也被忽略将会抛出异常
Assertions.assertThrows(IllegalArgumentException.class, () -> HierarchyIterator.depthFirst(node, node1 -> node1.children, node1 -> false));
// 忽略所有节点将会抛出NoSuchElementException
HierarchyIterator<Node> iterator = HierarchyIterator.depthFirst(node, node1 -> node1.children, node1 -> node1.id.equals("1"));
Assertions.assertSame(node, iterator.next());
Assertions.assertThrows(NoSuchElementException.class, iterator::next);
}
@Test
void testDepthFirstWithFilter() {
Node node = createTree();
// 按深度度优先遍历树结构忽略id为1-1的节点与以其为根节点的子树
List<String> nodes = new ArrayList<>();
HierarchyIterator<Node> iterator = HierarchyIterator.depthFirst(
node, node1 -> node1.children, node1 -> !node1.id.equals("1-1")
);
while (iterator.hasNext()) {
Node node1 = iterator.next();
nodes.add(node1.id);
}
Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), nodes);
}
@Test
void testDepthFirst() {
Node node = createTree();
// 按深度度优先遍历树结构
List<String> nodes = new ArrayList<>();
HierarchyIterator<Node> iterator = HierarchyIterator.depthFirst(node, node1 -> node1.children);
while (iterator.hasNext()) {
Node node1 = iterator.next();
nodes.add(node1.id);
}
Assertions.assertEquals(Arrays.asList("1", "1-1", "1-1-1", "1-1-2", "1-2", "1-2-1", "1-2-2"), nodes);
}
@Test
void testBreadthFirstWithFilter() {
Node node = createTree();
// 按深度度优先遍历树结构忽略id为1-1的节点与以其为根节点的子树
List<String> nodes = new ArrayList<>();
HierarchyIterator<Node> iterator = HierarchyIterator.breadthFirst(
node, node1 -> node1.children, node1 -> !node1.id.equals("1-1")
);
while (iterator.hasNext()) {
Node node1 = iterator.next();
nodes.add(node1.id);
}
Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), nodes);
}
@Test
void testBreadthFirst() {
Node root = createGraph();
// 按深度度优先遍历图结构
List<String> nodes = new ArrayList<>();
HierarchyIterator<Node> iterator = HierarchyIterator.breadthFirst(root, node -> node.children);
while (iterator.hasNext()) {
Node node = iterator.next();
nodes.add(node.id);
}
Assertions.assertEquals(Arrays.asList("1", "4", "2", "3"), nodes);
}
private static Node createGraph() {
// 构建一个包含四个节点的图每一个节点都有两个相邻节点
Node node1 = new Node("1");
Node node2 = new Node("2");
Node node3 = new Node("3");
Node node4 = new Node("4");
node1.children = Arrays.asList(node4, node2);
node2.children = Arrays.asList(node1, node3);
node3.children = Arrays.asList(node2, node4);
node4.children = Arrays.asList(node3, node1);
return node1;
}
private static Node createTree() {
// 构建一个三层的树每一个节点都有两个子节点
return new Node("1", Arrays.asList(
new Node("1-1", Arrays.asList(
new Node("1-1-1", null),
new Node("1-1-2", null)
)),
new Node("1-2", Arrays.asList(
new Node("1-2-1", null),
new Node("1-2-2", null)
))
));
}
/**
* 节点
*/
private static class Node {
private final String id;
private List<Node> children;
private Node(String id, List<Node> children) {
this.id = id;
this.children = children;
}
public Node(String id) {
this.id = id;
}
}
}

View File

@ -1,127 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.tree.hierarchy;
import lombok.Data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.*;
/**
* test for {@link HierarchyUtil}
*
* @author huangchengxing
*/
class HierarchyUtilTest {
@Data
static class Node {
private String parent;
private String value;
private List<Node> children;
public Node(final String parent, final String value) {
this.parent = parent;
this.value = value;
}
}
private Node root;
@BeforeEach
void init() {
// 根节点
root = new Node(null, "0");
// 第二层
final Node first1 = new Node(root.value, "0-1");
final Node first2 = new Node(root.value, "0-2");
final Node first3 = new Node(root.value, "0-3");
root.setChildren(Arrays.asList(first1, first2, first3));
// 第三层
final Node second11 = new Node(first1.value, "0-1-1");
final Node second12 = new Node(first1.value, "0-1-2");
final Node second13 = new Node(first1.value, "0-1-3");
first1.setChildren(Arrays.asList(second11, second12, second13));
final Node second21 = new Node(first2.value, "0-2-1");
final Node second22 = new Node(first2.value, "0-2-2");
final Node second23 = new Node(first2.value, "0-2-3");
first2.setChildren(Arrays.asList(second21, second22, second23));
final Node second31 = new Node(first3.value, "0-3-1");
final Node second32 = new Node(first3.value, "0-3-2");
final Node second33 = new Node(first3.value, "0-3-3");
first3.setChildren(Arrays.asList(second31, second32, second33));
}
@Test
void testTraverseByBreadthFirst() {
// // 按广度优先遍历所有节点
final List<String> nodes = new ArrayList<>();
HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.scan(t -> {
nodes.add(t.getValue());
return t.getChildren();
}));
Assertions.assertEquals(13, nodes.size());
Assertions.assertEquals(
Arrays.asList("0", "0-1", "0-2", "0-3", "0-1-1", "0-1-2", "0-1-3", "0-2-1", "0-2-2", "0-2-3", "0-3-1", "0-3-2", "0-3-3"),
nodes
);
// 按广度优先寻找 0-2-3
final String target = HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.find(Node::getChildren,
t -> Objects.equals(t.getValue(), "0-2-3") ? t.getValue() : null
));
Assertions.assertEquals("0-2-3", target);
// 按广度优先获取 0-2 的所有子节点
final List<String> children = HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.collect(Node::getChildren,
t -> Objects.equals(t.getParent(), "0-2") ? t.getValue() : null
));
Assertions.assertEquals(3, children.size());
Assertions.assertEquals(new ArrayList<>(Arrays.asList("0-2-1", "0-2-2", "0-2-3")), children);
}
@Test
void testTraverseByDepthFirst() {
// 按深度优先遍历所有节点
final Set<String> nodes = new LinkedHashSet<>();
HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.scan(t -> {
nodes.add(t.getValue());
return t.getChildren();
}));
Assertions.assertEquals(13, nodes.size());
Assertions.assertEquals(
new LinkedHashSet<>(Arrays.asList("0", "0-1", "0-1-1", "0-1-2", "0-1-3", "0-2", "0-2-1", "0-2-2", "0-2-3", "0-3", "0-3-1", "0-3-2", "0-3-3")),
nodes
);
// 按深度优先寻找 0-2-3
final String target = HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.find(Node::getChildren,
t -> Objects.equals(t.getValue(), "0-2-3") ? t.getValue() : null
));
Assertions.assertEquals("0-2-3", target);
// 按深度优先获取 0-2 的所有子节点
final List<String> children = HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.collect(Node::getChildren,
t -> Objects.equals(t.getParent(), "0-2") ? t.getValue() : null
));
Assertions.assertEquals(3, children.size());
Assertions.assertEquals(new ArrayList<>(Arrays.asList("0-2-1", "0-2-2", "0-2-3")), children);
}
}