!818 添加支持处理无向图结构的Map集合

Merge pull request !818 from Createsequence/v6-graph
This commit is contained in:
Looly 2022-09-26 04:08:45 +00:00 committed by Gitee
commit 06ee2e0fcd
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
3 changed files with 301 additions and 7 deletions

View File

@ -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);
}

View 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;
}
}

View 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));
}
}