From c3244a2c60112c7550586b2a401b1e91fc5fc971 Mon Sep 17 00:00:00 2001 From: Wangshu Pang Date: Sun, 2 Aug 2026 18:40:49 -0700 Subject: [PATCH 1/2] Implement node dissolution logic in NonLeafHelper and add entry collection methods --- .../rtree/internal/NonLeafHelper.java | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/davidmoten/rtree/internal/NonLeafHelper.java b/src/main/java/com/github/davidmoten/rtree/internal/NonLeafHelper.java index 9f110462..be4d7c32 100644 --- a/src/main/java/com/github/davidmoten/rtree/internal/NonLeafHelper.java +++ b/src/main/java/com/github/davidmoten/rtree/internal/NonLeafHelper.java @@ -7,6 +7,7 @@ import com.github.davidmoten.rtree.Context; import com.github.davidmoten.rtree.Entry; +import com.github.davidmoten.rtree.Leaf; import com.github.davidmoten.rtree.Node; import com.github.davidmoten.rtree.NonLeaf; import com.github.davidmoten.rtree.geometry.Geometry; @@ -110,14 +111,42 @@ public static NodeAndEntries delete( else { List> nodes = Util.remove(children, removeTheseNodes); nodes.addAll(addTheseNodes); - if (nodes.isEmpty()) - return new NodeAndEntries<>(Optional.empty(), addTheseEntries, - countDeleted); - else { + if (nodes.size() < node.context().minChildren()) { + // this node has fallen below minChildren so it must be + // dissolved: all entries in its remaining children (which may + // themselves be non-leaf nodes) are collected and passed up + // to be redistributed (re-added) higher up the tree, exactly + // as happens for leaves in LeafHelper.delete + List> entries = new ArrayList>(addTheseEntries); + entries.addAll(getEntries(nodes)); + return new NodeAndEntries<>(Optional.empty(), entries, countDeleted); + } else { NonLeaf nd = node.context().factory().createNonLeaf(nodes, node.context()); return new NodeAndEntries<>(of(nd), addTheseEntries, countDeleted); } } } + private static List> getEntries( + List> nodes) { + List> list = new ArrayList>(); + for (Node node : nodes) { + addEntries(node, list); + } + return list; + } + + private static void addEntries(Node node, + List> list) { + if (node instanceof Leaf) { + list.addAll(((Leaf) node).entries()); + } else if (node instanceof NonLeaf) { + NonLeaf n = (NonLeaf) node; + int count = n.count(); + for (int i = 0; i < count; i++) { + addEntries((Node) n.child(i), list); + } + } + } + } From 2119b942a62f95457f5a272905dd456e1d462f45 Mon Sep 17 00:00:00 2001 From: Wangshu Pang Date: Sun, 2 Aug 2026 23:49:15 -0700 Subject: [PATCH 2/2] Add test for non-leaf node dissolution in RTree --- .../github/davidmoten/rtree/RTreeTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/test/java/com/github/davidmoten/rtree/RTreeTest.java b/src/test/java/com/github/davidmoten/rtree/RTreeTest.java index c526bf17..6f8d88d0 100644 --- a/src/test/java/com/github/davidmoten/rtree/RTreeTest.java +++ b/src/test/java/com/github/davidmoten/rtree/RTreeTest.java @@ -1073,6 +1073,51 @@ public void testDeleteIssue81f() { assertEquals(0, t.size()); } + @Test + public void testDeleteIssue196() { + // non-leaf nodes must dissolve (not just be kept as-is) once their + // child count falls below minChildren, cascading up through the tree + int maxChildren = 3; + int minChildren = 2; + RTree tree = RTree.maxChildren(maxChildren).minChildren(minChildren) + .create(); + List> entries = new ArrayList>(); + for (int i = 1; i <= 30; i++) { + entries.add(e(i)); + } + for (Entry entry : entries) { + tree = tree.add(entry); + } + assertTrue(tree.calculateDepth() > 2); + for (int i = 0; i < entries.size() - 1; i++) { + tree = tree.delete(entries.get(i)); + assertFalse(tree.entries().contains(entries.get(i)).toBlocking().single()); + assertMinChildrenRespected(tree.root(), minChildren); + } + assertEquals(1, tree.size()); + } + + private static void assertMinChildrenRespected(Optional> root, + int minChildren) { + if (root.isPresent()) { + assertMinChildrenRespected(root.get(), minChildren, true); + } + } + + private static void assertMinChildrenRespected(Node node, int minChildren, + boolean isRoot) { + if (node instanceof NonLeaf) { + NonLeaf n = (NonLeaf) node; + if (!isRoot) { + assertTrue("non-leaf node has " + n.count() + " children, less than minChildren=" + + minChildren, n.count() >= minChildren); + } + for (int i = 0; i < n.count(); i++) { + assertMinChildrenRespected(n.child(i), minChildren, false); + } + } + } + private static Func2 distanceCircleToPoint = new Func2() { @Override public Double call(Point point, Circle circle) {