This commit is contained in:
huangchengxing 2022-07-04 15:55:07 +08:00
parent 59b5a17ee6
commit 81cbb0d8a0
4 changed files with 108 additions and 26 deletions

View File

@ -53,7 +53,7 @@ public interface ForestMap<K, V> extends Map<K, TreeEntry<K, V>> {
treeEntryMap.forEach((k, v) -> { treeEntryMap.forEach((k, v) -> {
if (v.hasParent()) { if (v.hasParent()) {
final TreeEntry<K, V> parent = v.getDeclaredParent(); final TreeEntry<K, V> parent = v.getDeclaredParent();
putLinkedNode(parent.getKey(), parent.getValue(), v.getKey(), v.getValue()); putLinkedNodes(parent.getKey(), parent.getValue(), v.getKey(), v.getValue());
} else { } else {
putNode(v.getKey(), v.getValue()); putNode(v.getKey(), v.getValue());
} }
@ -99,7 +99,7 @@ public interface ForestMap<K, V> extends Map<K, TreeEntry<K, V>> {
values.forEach(v -> { values.forEach(v -> {
final K key = keyGenerator.apply(v); final K key = keyGenerator.apply(v);
final K parentKey = parentKeyGenerator.apply(v); final K parentKey = parentKeyGenerator.apply(v);
linkNode(parentKey, key); linkNodes(parentKey, key);
get(key).setValue(v); get(key).setValue(v);
}); });
} }
@ -124,42 +124,54 @@ public interface ForestMap<K, V> extends Map<K, TreeEntry<K, V>> {
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li> * <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul> * </ul>
* 该操作等同于 * 该操作等同于
* <pre> * <pre>{@code
* TreeEntry<K, V> parent = putNode(parentKey, parentValue); * putNode(parentKey, parentValue);
* TreeEntry<K, V> child = putNode(childKey, childValue); * putNode(childKey, childValue);
* linkNode(parentKey, childKey); * linkNodes(parentKey, childKey);
* </pre> * }</pre>
* *
* @param parentKey 父节点的key * @param parentKey 父节点的key
* @param parentValue 父节点的value * @param parentValue 父节点的value
* @param childKey 子节点的key * @param childKey 子节点的key
* @param childValue 子节点的值 * @param childValue 子节点的值
*/ */
default void putLinkedNode(K parentKey, V parentValue, K childKey, V childValue) { default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) {
linkNode(parentKey, childKey, (parent, child) -> { putNode(parentKey, parentValue);
parent.setValue(parentValue); putNode(childKey, childValue);
child.setValue(childValue); linkNodes(parentKey, childKey);
});
} }
/** /**
* 为指定的节点建立父子关系{@code parentKey}{@code childKey}对应节点不存在则会创建一个对应的值为null的空节点 <br> * 添加子节点并为子节点指定父节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
void putLinkedNodes(K parentKey, K childKey, V childValue);
/**
* 为集合中的指定的节点建立父子关系
* *
* @param parentKey 父节点的key * @param parentKey 父节点的key
* @param childKey 子节点的key * @param childKey 子节点的key
*/ */
default void linkNode(K parentKey, K childKey) { default void linkNodes(K parentKey, K childKey) {
linkNode(parentKey, childKey, null); linkNodes(parentKey, childKey, null);
} }
/** /**
* 为指定的节点建立父子关系{@code parentKey}{@code childKey}对应节点不存在则会创建一个对应的值为null的空节点 * 集合中的指定的节点建立父子关系
* *
* @param parentKey 父节点的key * @param parentKey 父节点的key
* @param childKey 子节点的key * @param childKey 子节点的key
* @param consumer 对父节点和子节点的操作允许为null * @param consumer 对父节点和子节点的操作允许为null
*/ */
void linkNode(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer); void linkNodes(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer);
/** /**
* {@code parentKey}{@code childKey}对应节点都存在则移除指定该父节点与其直接关联的指定子节点间的引用关系 * {@code parentKey}{@code childKey}对应节点都存在则移除指定该父节点与其直接关联的指定子节点间的引用关系

View File

@ -210,6 +210,48 @@ public class LinkedForestMap<K, V> implements ForestMap<K, V> {
return null; return null;
} }
/**
* 同时添加父子节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
* 该操作等同于
* <pre>
* TreeEntry<K, V> parent = putNode(parentKey, parentValue);
* TreeEntry<K, V> child = putNode(childKey, childValue);
* linkNodes(parentKey, childKey);
* </pre>
*
* @param parentKey 父节点的key
* @param parentValue 父节点的value
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
@Override
public void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) {
linkNodes(parentKey, childKey, (parent, child) -> {
parent.setValue(parentValue);
child.setValue(childValue);
});
}
/**
* 添加子节点并为子节点指定父节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
@Override
public void putLinkedNodes(K parentKey, K childKey, V childValue) {
linkNodes(parentKey, childKey, (parent, child) -> child.setValue(childValue));
}
/** /**
* 为指定的节点建立父子关系{@code parentKey}{@code childKey}对应节点不存在则会创建一个对应的值为null的空节点 * 为指定的节点建立父子关系{@code parentKey}{@code childKey}对应节点不存在则会创建一个对应的值为null的空节点
* *
@ -218,7 +260,7 @@ public class LinkedForestMap<K, V> implements ForestMap<K, V> {
* @param consumer 对父节点和子节点的操作允许为null * @param consumer 对父节点和子节点的操作允许为null
*/ */
@Override @Override
public void linkNode(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer) { public void linkNodes(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer) {
consumer = ObjectUtil.defaultIfNull(consumer, (parent, child) -> {}); consumer = ObjectUtil.defaultIfNull(consumer, (parent, child) -> {});
final TreeEntryNode<K, V> parentNode = nodes.computeIfAbsent(parentKey, t -> new TreeEntryNode<>(null, t)); final TreeEntryNode<K, V> parentNode = nodes.computeIfAbsent(parentKey, t -> new TreeEntryNode<>(null, t));
TreeEntryNode<K, V> childNode = nodes.get(childKey); TreeEntryNode<K, V> childNode = nodes.get(childKey);
@ -358,6 +400,7 @@ public class LinkedForestMap<K, V> implements ForestMap<K, V> {
* *
* @return 当前节点与根节点的距离 * @return 当前节点与根节点的距离
*/ */
@Override
public int getWeight() { public int getWeight() {
return weight; return weight;
} }
@ -454,6 +497,17 @@ public class LinkedForestMap<K, V> implements ForestMap<K, V> {
return traverseParentNodes(false, p -> {}, p -> p.equalsKey(key)); return traverseParentNodes(false, p -> {}, p -> p.equalsKey(key));
} }
/**
* 获取以当前节点作为根节点的树结构然后遍历所有节点
*
* @param includeSelf 是否处理当前节点
* @param nodeConsumer 对节点的处理
*/
@Override
public void forEachChild(boolean includeSelf, Consumer<TreeEntry<K, V>> nodeConsumer) {
traverseChildNodes(includeSelf, (index, child) -> nodeConsumer.accept(child), null);
}
/** /**
* 指定key与当前节点的key是否相等 * 指定key与当前节点的key是否相等
* *
@ -577,7 +631,7 @@ public class LinkedForestMap<K, V> implements ForestMap<K, V> {
} }
/** /**
* 获取以当前节点作为根节点的树结构然后获取该树结构中的当前节点的全部子节点 * 获取以当前节点作为根节点的树结构然后按广度优先获取该树结构中的当前节点的全部子节点
* *
* @return 节点 * @return 节点
*/ */

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
/** /**
* 允许拥有一个父节点与多个子节点的{@link Map.Entry}实现 * 允许拥有一个父节点与多个子节点的{@link Map.Entry}实现
@ -40,6 +41,13 @@ public interface TreeEntry<K, V> extends Map.Entry<K, V> {
// ===================== 父节点相关方法 ===================== // ===================== 父节点相关方法 =====================
/**
* 获取以当前节点作为叶子节点的树结构然后获取当前节点与根节点的距离
*
* @return 当前节点与根节点的距离
*/
int getWeight();
/** /**
* 获取以当前节点作为叶子节点的树结构然后获取该树结构的根节点 * 获取以当前节点作为叶子节点的树结构然后获取该树结构的根节点
* *
@ -81,6 +89,14 @@ public interface TreeEntry<K, V> extends Map.Entry<K, V> {
return ObjectUtil.isNotNull(getParent(key)); return ObjectUtil.isNotNull(getParent(key));
} }
/**
* 获取以当前节点作为根节点的树结构然后遍历所有节点
*
* @param includeSelf 是否处理当前节点
* @param nodeConsumer 对节点的处理
*/
void forEachChild(boolean includeSelf, Consumer<TreeEntry<K, V>> nodeConsumer);
// ===================== 子节点相关方法 ===================== // ===================== 子节点相关方法 =====================
/** /**

View File

@ -31,9 +31,9 @@ public class LinkedForestMapTest {
@Before @Before
public void beforeTest() { public void beforeTest() {
// a -> b -> c -> d // a -> b -> c -> d
treeNodeMap.linkNode("a", "b"); treeNodeMap.linkNodes("a", "b");
treeNodeMap.linkNode("b", "c"); treeNodeMap.linkNodes("b", "c");
treeNodeMap.linkNode("c", "d"); treeNodeMap.linkNodes("c", "d");
} }
@Test @Test
@ -63,7 +63,7 @@ public class LinkedForestMapTest {
Assert.assertEquals(CollUtil.newLinkedHashSet("a", "b", "c", "d"), transKey(treeNodeMap.getTreeNodes("d"))); Assert.assertEquals(CollUtil.newLinkedHashSet("a", "b", "c", "d"), transKey(treeNodeMap.getTreeNodes("d")));
// 循环依赖 // 循环依赖
Assert.assertThrows(IllegalArgumentException.class, () -> treeNodeMap.linkNode("d", "a")); Assert.assertThrows(IllegalArgumentException.class, () -> treeNodeMap.linkNodes("d", "a"));
} }
@Test @Test
@ -93,7 +93,7 @@ public class LinkedForestMapTest {
@Test @Test
public void testReRegisterAfterRemove() { public void testReRegisterAfterRemove() {
// a -> b -> c -> d // a -> b -> c -> d
treeNodeMap.linkNode("b", "c"); treeNodeMap.linkNodes("b", "c");
testRegisterBeforeRemove(); testRegisterBeforeRemove();
} }
@ -101,8 +101,8 @@ public class LinkedForestMapTest {
public void testParallelRegister() { public void testParallelRegister() {
// a -> b -> c --> d // a -> b -> c --> d
// | -> c2 -> d2 // | -> c2 -> d2
treeNodeMap.linkNode("b", "c2"); treeNodeMap.linkNodes("b", "c2");
treeNodeMap.linkNode("c2", "d2"); treeNodeMap.linkNodes("c2", "d2");
// getDeclaredChildren // getDeclaredChildren
Assert.assertEquals(CollUtil.newLinkedHashSet("b", "c", "d", "c2", "d2"), transKey(treeNodeMap.getChildNodes("a"))); Assert.assertEquals(CollUtil.newLinkedHashSet("b", "c", "d", "c2", "d2"), transKey(treeNodeMap.getChildNodes("a")));