mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-24 18:04:54 +08:00
!818 添加支持处理无向图结构的Map集合
Merge pull request !818 from Createsequence/v6-graph
This commit is contained in:
commit
06ee2e0fcd
@ -2,8 +2,7 @@ package cn.hutool.core.annotation;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.multi.MultiValueMap;
|
||||
import cn.hutool.core.map.multi.SetValueMap;
|
||||
import cn.hutool.core.map.multi.Graph;
|
||||
import cn.hutool.core.reflect.ClassUtil;
|
||||
import cn.hutool.core.reflect.MethodUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
@ -479,8 +478,8 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
private void resolveAliasAttributes() {
|
||||
final Map<Method, Integer> attributeIndexes = new HashMap<>(attributes.length);
|
||||
|
||||
final Graph<Method> methodGraph = new Graph<>();
|
||||
// 解析被作为别名的关联属性,根据节点关系构建邻接表
|
||||
final MultiValueMap<Method, Method> aliasedMethods = new SetValueMap<>();
|
||||
for (int i = 0; i < attributes.length; i++) {
|
||||
// 获取属性上的@Alias注解
|
||||
final Method attribute = attributes[i];
|
||||
@ -492,15 +491,14 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
// 获取别名属性
|
||||
final Method aliasAttribute = getAliasAttribute(attribute, attributeAnnotation);
|
||||
Objects.requireNonNull(aliasAttribute);
|
||||
aliasedMethods.putValue(aliasAttribute, attribute);
|
||||
aliasedMethods.putValue(attribute, aliasAttribute);
|
||||
methodGraph.putEdge(aliasAttribute, attribute);
|
||||
}
|
||||
|
||||
// 按广度优先遍历邻接表,将属于同一张图上的节点分为一组,并为其建立AliasSet
|
||||
final Set<Method> accessed = new HashSet<>(attributes.length);
|
||||
final Set<Method> group = new LinkedHashSet<>();
|
||||
final Deque<Method> deque = new LinkedList<>();
|
||||
for (final Method target : aliasedMethods.keySet()) {
|
||||
for (final Method target : methodGraph.keySet()) {
|
||||
group.clear();
|
||||
deque.addLast(target);
|
||||
while (!deque.isEmpty()) {
|
||||
@ -512,7 +510,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
accessed.add(curr);
|
||||
// 将其添加到关系组
|
||||
group.add(curr);
|
||||
Collection<Method> aliases = aliasedMethods.get(curr);
|
||||
final Collection<Method> aliases = methodGraph.getAdjacentPoints(curr);
|
||||
if (CollUtil.isNotEmpty(aliases)) {
|
||||
deque.addAll(aliases);
|
||||
}
|
||||
|
141
hutool-core/src/main/java/cn/hutool/core/map/multi/Graph.java
Normal file
141
hutool-core/src/main/java/cn/hutool/core/map/multi/Graph.java
Normal file
@ -0,0 +1,141 @@
|
||||
package cn.hutool.core.map.multi;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 支持处理无向图结构的{@link Map},本质上是基于{@link SetValueMap}实现的邻接表
|
||||
*
|
||||
* @param <T> 节点类型
|
||||
* @author huangchengxing
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class Graph<T> extends SetValueMap<T, T> {
|
||||
|
||||
/**
|
||||
* 添加边
|
||||
*
|
||||
* @param target1 节点
|
||||
* @param target2 节点
|
||||
*/
|
||||
public void putEdge(final T target1, final T target2) {
|
||||
this.putValue(target1, target2);
|
||||
this.putValue(target2, target1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在边
|
||||
*
|
||||
* @param target1 节点
|
||||
* @param target2 节点
|
||||
* @return 是否
|
||||
*/
|
||||
public boolean containsEdge(final T target1, final T target2) {
|
||||
return this.getValues(target1).contains(target2)
|
||||
&& this.getValues(target2).contains(target1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除边
|
||||
*
|
||||
* @param target1 节点
|
||||
* @param target2 节点
|
||||
*/
|
||||
public void removeEdge(final T target1, final T target2) {
|
||||
this.removeValue(target1, target2);
|
||||
this.removeValue(target2, target1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除节点,并删除该节点与其他节点之间连成的边
|
||||
*
|
||||
* @param target 目标对象
|
||||
*/
|
||||
public void removePoint(final T target) {
|
||||
final Collection<T> associatedPoints = this.remove(target);
|
||||
if (CollUtil.isNotEmpty(associatedPoints)) {
|
||||
associatedPoints.forEach(p -> this.removeValue(p, target));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 两节点是否存在直接或间接的关联
|
||||
*
|
||||
* @param target1 节点
|
||||
* @param target2 节点
|
||||
* @return 两节点是否存在关联
|
||||
*/
|
||||
public boolean containsAssociation(final T target1, final T target2) {
|
||||
if (!this.containsKey(target1) || !this.containsKey(target2)) {
|
||||
return false;
|
||||
}
|
||||
final AtomicBoolean flag = new AtomicBoolean(false);
|
||||
visitAssociatedPoints(target1, t -> {
|
||||
if (Objects.equals(t, target2)) {
|
||||
flag.set(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return flag.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按广度优先,获得节点的所有直接或间接关联的节点,节点默认按添加顺序排序
|
||||
*
|
||||
* @param target 节点
|
||||
* @param includeTarget 是否包含查询节点
|
||||
* @return 节点的所有关联节点
|
||||
*/
|
||||
public Collection<T> getAssociatedPoints(final T target, final boolean includeTarget) {
|
||||
final Set<T> points = visitAssociatedPoints(target, t -> false);
|
||||
if (!includeTarget) {
|
||||
points.remove(target);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的邻接节点
|
||||
*
|
||||
* @param target 节点
|
||||
* @return 邻接节点
|
||||
*/
|
||||
public Collection<T> getAdjacentPoints(final T target) {
|
||||
return this.getValues(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按广度优先,访问节点的所有关联节点
|
||||
*/
|
||||
private Set<T> visitAssociatedPoints(final T key, final Predicate<T> breaker) {
|
||||
if (!this.containsKey(key)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
final Set<T> accessed = new HashSet<>();
|
||||
final Deque<T> deque = new LinkedList<>();
|
||||
deque.add(key);
|
||||
while (!deque.isEmpty()) {
|
||||
// 访问节点
|
||||
final T t = deque.removeFirst();
|
||||
if (accessed.contains(t)) {
|
||||
continue;
|
||||
}
|
||||
accessed.add(t);
|
||||
// 若符合条件则中断循环
|
||||
if (breaker.test(t)) {
|
||||
break;
|
||||
}
|
||||
// 获取邻接节点
|
||||
final Collection<T> neighbours = this.getValues(t);
|
||||
if (!neighbours.isEmpty()) {
|
||||
deque.addAll(neighbours);
|
||||
}
|
||||
}
|
||||
return accessed;
|
||||
}
|
||||
|
||||
}
|
155
hutool-core/src/test/java/cn/hutool/core/map/GraphTest.java
Normal file
155
hutool-core/src/test/java/cn/hutool/core/map/GraphTest.java
Normal file
@ -0,0 +1,155 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import cn.hutool.core.map.multi.Graph;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* test for {@link Graph}
|
||||
*/
|
||||
public class GraphTest {
|
||||
|
||||
@Test
|
||||
public void testPutEdge() {
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 0);
|
||||
|
||||
Assert.assertEquals(asSet(1, 2), graph.getValues(0));
|
||||
Assert.assertEquals(asSet(0, 2), graph.getValues(1));
|
||||
Assert.assertEquals(asSet(0, 1), graph.getValues(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsEdge() {
|
||||
// 0 -- 1
|
||||
// | |
|
||||
// 3 -- 2
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 3);
|
||||
graph.putEdge(3, 0);
|
||||
|
||||
Assert.assertTrue(graph.containsEdge(0, 1));
|
||||
Assert.assertTrue(graph.containsEdge(1, 0));
|
||||
|
||||
Assert.assertTrue(graph.containsEdge(1, 2));
|
||||
Assert.assertTrue(graph.containsEdge(2, 1));
|
||||
|
||||
Assert.assertTrue(graph.containsEdge(2, 3));
|
||||
Assert.assertTrue(graph.containsEdge(3, 2));
|
||||
|
||||
Assert.assertTrue(graph.containsEdge(3, 0));
|
||||
Assert.assertTrue(graph.containsEdge(0, 3));
|
||||
|
||||
Assert.assertFalse(graph.containsEdge(1, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeEdge() {
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
Assert.assertTrue(graph.containsEdge(0, 1));
|
||||
|
||||
graph.removeEdge(0, 1);
|
||||
Assert.assertFalse(graph.containsEdge(0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsAssociation() {
|
||||
// 0 -- 1
|
||||
// | |
|
||||
// 3 -- 2
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 3);
|
||||
graph.putEdge(3, 0);
|
||||
|
||||
Assert.assertTrue(graph.containsAssociation(0, 2));
|
||||
Assert.assertTrue(graph.containsAssociation(2, 0));
|
||||
|
||||
Assert.assertTrue(graph.containsAssociation(1, 3));
|
||||
Assert.assertTrue(graph.containsAssociation(3, 1));
|
||||
|
||||
Assert.assertFalse(graph.containsAssociation(-1, 1));
|
||||
Assert.assertFalse(graph.containsAssociation(1, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAssociationPoints() {
|
||||
// 0 -- 1
|
||||
// | |
|
||||
// 3 -- 2
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 3);
|
||||
graph.putEdge(3, 0);
|
||||
|
||||
Assert.assertEquals(asSet(0, 1, 2, 3), graph.getAssociatedPoints(0, true));
|
||||
Assert.assertEquals(asSet(1, 2, 3), graph.getAssociatedPoints(0, false));
|
||||
|
||||
Assert.assertEquals(asSet(1, 2, 3, 0), graph.getAssociatedPoints(1, true));
|
||||
Assert.assertEquals(asSet(2, 3, 0), graph.getAssociatedPoints(1, false));
|
||||
|
||||
Assert.assertEquals(asSet(2, 3, 0, 1), graph.getAssociatedPoints(2, true));
|
||||
Assert.assertEquals(asSet(3, 0, 1), graph.getAssociatedPoints(2, false));
|
||||
|
||||
Assert.assertEquals(asSet(3, 0, 1, 2), graph.getAssociatedPoints(3, true));
|
||||
Assert.assertEquals(asSet(0, 1, 2), graph.getAssociatedPoints(3, false));
|
||||
|
||||
Assert.assertTrue(graph.getAssociatedPoints(-1, false).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAdjacentPoints() {
|
||||
// 0 -- 1
|
||||
// | |
|
||||
// 3 -- 2
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 3);
|
||||
graph.putEdge(3, 0);
|
||||
|
||||
Assert.assertEquals(asSet(1, 3), graph.getAdjacentPoints(0));
|
||||
Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(1));
|
||||
Assert.assertEquals(asSet(1, 3), graph.getAdjacentPoints(2));
|
||||
Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovePoint() {
|
||||
// 0 -- 1
|
||||
// | |
|
||||
// 3 -- 2
|
||||
Graph<Integer> graph = new Graph<>();
|
||||
graph.putEdge(0, 1);
|
||||
graph.putEdge(1, 2);
|
||||
graph.putEdge(2, 3);
|
||||
graph.putEdge(3, 0);
|
||||
|
||||
// 0
|
||||
// |
|
||||
// 3 -- 2
|
||||
graph.removePoint(1);
|
||||
|
||||
Assert.assertEquals(asSet(3), graph.getAdjacentPoints(0));
|
||||
Assert.assertTrue(graph.getAdjacentPoints(1).isEmpty());
|
||||
Assert.assertEquals(asSet(3), graph.getAdjacentPoints(2));
|
||||
Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(3));
|
||||
}
|
||||
|
||||
|
||||
private static <T> Set<T> asSet(T... ts) {
|
||||
return new LinkedHashSet<>(Arrays.asList(ts));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user