From 573d65d02bd4d07762c9120734d964ffbf307b5a Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Wed, 3 May 2023 19:50:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(HierarchyUtil):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=A4=84=E7=90=86=E5=B1=82=E7=BA=A7=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E7=9A=84=E9=80=9A=E7=94=A8=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lang/hierarchy/HierarchyIterator.java | 221 ++++++++++++++++++ .../core/lang/hierarchy/HierarchyUtil.java | 140 +++++++++++ .../lang/hierarchy/HierarchyUtilTest.java | 127 ++++++++++ 3 files changed, 488 insertions(+) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyIterator.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtil.java create mode 100644 hutool-core/src/test/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtilTest.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyIterator.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyIterator.java new file mode 100644 index 000000000..e8d6c054e --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyIterator.java @@ -0,0 +1,221 @@ +package org.dromara.hutool.core.lang.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.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * 用于遍历层级结构的迭代器,提供了遍历过程中的回调方法: + * + * + *

默认提供了三类{@link HierarchyIterator}实现: + *

+ * 可以实现自定义的{@link HierarchyIterator}来支持更多的遍历模式。 + * + * @param 层级类型 + * @param 结果类型 + * @author huangchengxing + * @since 6.0.0 + */ +@FunctionalInterface +public interface HierarchyIterator { + + /** + * 获取下一层级 + * + * @param result 结果 + * @param hierarchy 当前层级 + * @return 向容器中添加元素的方法 + */ + Collection nextHierarchies(R result, H hierarchy); + + /** + * 是否中断遍历 + * + * @param hierarchy 当前层级 + * @return 是否中断遍历 + */ + default boolean isBreak(H hierarchy) { + return false; + } + + /** + * 获取结果 + * + * @return 结果 + */ + default R getResult() { + return null; + } + + /** + * 创建一个{@link HierarchyIterator}对象, 当{@code finder}返回非空时, 迭代器立刻中断, 返回结果 + * + * @param function 迭代器处理函数 + * @param finder 查找器 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @return {@link HierarchyIterator} + */ + static HierarchyIterator find( + final Function> function, final Function finder) { + Objects.requireNonNull(function); + Objects.requireNonNull(finder); + final Mutable mutable = Mutable.of(null); + final Predicate terminator = h -> { + 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)); + } + + /** + * 创建一个{@link HierarchyIterator}对象, 迭代器结果总是为{@code null} + * + * @param function 迭代器处理函数 + * @param terminator 是否终止遍历 + * @param 层级结构类型 + * @return {@link HierarchyIterator} + */ + static HierarchyIterator scan( + final Function> function, final Predicate terminator) { + Objects.requireNonNull(function); + return new HierarchyIteratorImpl<>(() -> null, terminator, (r, h) -> function.apply(h)); + } + + /** + * 创建一个{@link HierarchyIterator}对象, 迭代器结果总是为{@code null} + * + * @param function 迭代器处理函数 + * @param 层级结构类型 + * @return {@link HierarchyIterator} + */ + static HierarchyIterator scan(final Function> function) { + return scan(function, h -> false); + } + + /** + * 创建一个{@link HierarchyIterator}对象, 若{@code mapper}返回非空, 则将结果添加到集合中,最终返回集合 + * + * @param function 迭代器处理函数 + * @param collFactory 集合工厂 + * @param mapper 迭代器结果映射函数 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @param 集合类型 + * @return {@link HierarchyIterator} + */ + static > HierarchyIterator collect( + final Function> function, final Supplier collFactory, final Function 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); + }); + } + + /** + * 创建一个{@link HierarchyIterator}对象, 若{@code mapper}返回非空, 则将结果添加到集合中,最终返回集合 + * + * @param function 迭代器处理函数 + * @param mapper 迭代器结果映射函数 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @return {@link HierarchyIterator} + */ + static HierarchyIterator> collect( + final Function> function, final Function mapper) { + return collect(function, ArrayList::new, mapper); + } + + /** + * 创建一个{@link HierarchyIterator}对象, 则将非空结果添加到集合中,最终返回集合 + * + * @param function 迭代器处理函数 + * @param 层级结构类型 + * @return {@link HierarchyIterator} + */ + static HierarchyIterator> collect( + final Function> function) { + return collect(function, Function.identity()); + } + + /** + * {@link HierarchyIterator}的基本实现。 + * + * @param 层级类型 + * @param 结果类型 + */ + class HierarchyIteratorImpl implements HierarchyIterator { + + private final Supplier resultFactory; + private final Predicate hierarchyFilter; + private final BiFunction> hierarchyFinder; + + public HierarchyIteratorImpl( + final Supplier resultFactory, final Predicate hierarchyFilter, + final BiFunction> hierarchyFinder) { + this.resultFactory = resultFactory; + this.hierarchyFilter = hierarchyFilter; + this.hierarchyFinder = hierarchyFinder; + } + + /** + * 获取下一层级 + * + * @param result 结果 + * @param hierarchy 当前层级 + * @return 向容器中添加元素的方法 + */ + @Override + public Collection 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(); + } + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtil.java new file mode 100644 index 000000000..77abe2cfd --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtil.java @@ -0,0 +1,140 @@ +package org.dromara.hutool.core.lang.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; + +/** + *

通用层级结构扫描器工具类。
+ * + *

用于根据广度优先或深度优先或搜索遍历如树或图等特定数据结构的层级,如:文件夹层级、类层级、方法或注解层级等。 + * 使用时指定遍历的起始节点,以及遍历算法,并配合{@link HierarchyIterator}即可完成遍历中的业务逻辑处理。 + * + *

比如,我们可以使用其来访问树结构: + *

{@code
+ * // 构建一个树形结构
+ * Node root = Tree.build();
+ * // 从树结构中通过深度优先查找某个节点
+ * Node target = HierarchyUtil.traverseByDepthFirst(
+ * 	root, HierarchyUtil.finder(Node::getChildren, node -> Objects.equals(node.getId(), "target") ? node : null)
+ * );
+ * // 从树结构中通过广度优先获取其所有的子节点
+ * List nodes = HierarchyUtil.traverseByBreadthFirst(
+ * 	root, HierarchyUtil.collector(Node::getChildren)
+ * );
+ * }
+ * + * @author huangchengxing + * @see HierarchyIterator + * @since 6.0.0 + */ +public class HierarchyUtil { + + // ================ breadth first ================ + + /** + * 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构 + * + * @param hierarchy 层级结构 + * @param filter 过滤器,校验不通过的层级结构不会被查找 + * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @return 迭代器结果 + */ + public static R traverseByBreadthFirst( + final H hierarchy, final Predicate filter, final HierarchyIterator hierarchyIterator) { + return scanHierarchies(hierarchy, filter, hierarchyIterator, Collection::addAll); + } + + /** + * 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构 + * + * @param hierarchy 层级结构 + * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @return 迭代器结果 + */ + public static R traverseByBreadthFirst(final H hierarchy, final HierarchyIterator hierarchyIterator) { + return traverseByBreadthFirst(hierarchy, h -> true, hierarchyIterator); + } + + // ================ depth first ================ + + /** + * 按深度优先遍历指定包括{@code hierarchy}本身在内层级结构 + * + * @param hierarchy 层级结构 + * @param filter 过滤器,校验不通过的层级结构不会被查找 + * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 + * @param 层级结构类型 + * @param 迭代器结果类型 + * @return 迭代器结果 + */ + public static R traverseByDepthFirst( + final H hierarchy, final Predicate filter, final HierarchyIterator 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 层级结构类型 + * @param 迭代器结果类型 + * @return 迭代器结果 + */ + public static R traverseByDepthFirst(final H hierarchy, final HierarchyIterator hierarchyIterator) { + return traverseByDepthFirst(hierarchy, h -> true, hierarchyIterator); + } + + private static R scanHierarchies( + final H hierarchy, final Predicate filter, final HierarchyIterator hierarchyIterator, + final BiConsumer, Collection> appender) { + Objects.requireNonNull(hierarchy); + Objects.requireNonNull(filter); + Objects.requireNonNull(hierarchyIterator); + + // 遍历层级结构 + final Predicate hierarchyFilter = filter.negate(); + final LinkedList queue = new LinkedList<>(); + final Set accessed = new HashSet<>(); + queue.add(hierarchy); + while (!queue.isEmpty()) { + // 跳过已经访问过或者被过滤的层级结构 + final H curr = queue.removeFirst(); + if (accessed.contains(curr) || hierarchyFilter.test(curr)) { + continue; + } + accessed.add(curr); + + // 若迭代器返回true,则结束遍历 + if (hierarchyIterator.isBreak(curr)) { + return hierarchyIterator.getResult(); + } + + // 获取下一需要遍历的层级 + final Collection nextHierarchies = hierarchyIterator.nextHierarchies(hierarchyIterator.getResult(), curr); + if (CollUtil.isEmpty(nextHierarchies)) { + continue; + } + appender.accept(queue, nextHierarchies); + } + return hierarchyIterator.getResult(); + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtilTest.java new file mode 100644 index 000000000..acdd5ca1f --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/hierarchy/HierarchyUtilTest.java @@ -0,0 +1,127 @@ +package org.dromara.hutool.core.lang.hierarchy; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * test for {@link HierarchyUtil} + * + * @author huangchengxing + */ +class HierarchyUtilTest { + + private Map tree; + + @BeforeEach + void init() { + tree = new LinkedHashMap<>(); + + // 根节点 + tree.put("0", null); + + // 第一层 + tree.put("0-1", "0"); + tree.put("0-2", "0"); + tree.put("0-3", "0"); + + // 第三层 + tree.put("0-1-1", "0-1"); + tree.put("0-1-2", "0-1"); + tree.put("0-1-3", "0-1"); + + tree.put("0-2-1", "0-2"); + tree.put("0-2-2", "0-2"); + tree.put("0-2-3", "0-2"); + + tree.put("0-3-1", "0-3"); + tree.put("0-3-2", "0-3"); + tree.put("0-3-3", "0-3"); + } + + @Test + void testTraverseByBreadthFirst() { + // 按广度优先遍历所有节点 + Set nodes = new LinkedHashSet<>(); + HierarchyUtil.traverseByBreadthFirst("0", HierarchyIterator.scan(t -> { + nodes.add(t); + return tree.entrySet().stream() + .filter(e -> Objects.equals(t, e.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + })); + Assertions.assertEquals(13, nodes.size()); + Assertions.assertEquals( + new LinkedHashSet<>(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 + String target = HierarchyUtil.traverseByBreadthFirst("0", HierarchyIterator.find(parentFinder(), + t -> Objects.equals(t, "0-2-3") ? t : null + )); + Assertions.assertEquals("0-2-3", target); + + // 按广度优先获取 0-2 的所有子节点 + List children = HierarchyUtil.traverseByBreadthFirst( + "0", HierarchyIterator.collect(parentFinder(), + t -> Objects.equals(tree.get(t), "0-2") ? t : 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() { + // 按深度优先遍历所有节点 + Set nodes = new LinkedHashSet<>(); + HierarchyUtil.traverseByDepthFirst("0", HierarchyIterator.scan(t -> { + nodes.add(t); + return tree.entrySet().stream() + .filter(e -> Objects.equals(t, e.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + })); + 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 + String target = HierarchyUtil.traverseByDepthFirst("0", HierarchyIterator.find(parentFinder(), + t -> Objects.equals(t, "0-2-3") ? t : null + )); + Assertions.assertEquals("0-2-3", target); + + // 按深度优先获取 0-2 的所有子节点 + List children = HierarchyUtil.traverseByDepthFirst( + "0", HierarchyIterator.collect(parentFinder(), + t -> Objects.equals(tree.get(t), "0-2") ? t : null + ) + ); + Assertions.assertEquals(3, children.size()); + Assertions.assertEquals(new ArrayList<>(Arrays.asList("0-2-1", "0-2-2", "0-2-3")), children); + } + + private Function> parentFinder() { + return t -> tree.entrySet() + .stream() + .filter(e -> Objects.equals(t, e.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } +}