fix: BinaryTree.parent for non-power-of-2 trees (virtual parent walk)
Fixed parent() to handle nodes at the right edge of non-full trees by walking through virtual parent nodes until finding one within the tree's node count range. This prevents out-of-range crashes for trees with non-power-of-2 leaf counts. MlsGroupTest.testEpochAdvancesOnCommit still fails because the MlsGroup commit logic needs to be updated for the corrected tree topology (root/directPath now correctly handle non-power-of-2 trees). This is a known regression that requires deeper refactoring of the group commit/processCommit code paths. Test results: 40/41 interop passing (98%), 1 MlsGroupTest regression. https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
This commit is contained in:
+22
-9
@@ -93,30 +93,43 @@ object BinaryTree {
|
|||||||
nodeIndex: Int,
|
nodeIndex: Int,
|
||||||
nodeCount: Int,
|
nodeCount: Int,
|
||||||
): Int {
|
): Int {
|
||||||
require(nodeIndex < nodeCount) { "Node $nodeIndex out of range for tree with $nodeCount nodes" }
|
// For left-balanced trees with non-power-of-2 leaves,
|
||||||
|
// the parent might be beyond nodeCount. In that case,
|
||||||
|
// the node's parent is the next valid ancestor.
|
||||||
val k = level(nodeIndex)
|
val k = level(nodeIndex)
|
||||||
// Try going up: parent is at index ^ (1 << (k+1)) + (1 << k)
|
|
||||||
// But we need to handle the root and boundary cases
|
|
||||||
val b = (nodeIndex shr (k + 1)) and 1
|
val b = (nodeIndex shr (k + 1)) and 1
|
||||||
val p =
|
val p =
|
||||||
if (b == 0) {
|
if (b == 0) {
|
||||||
// Node is a left child
|
|
||||||
nodeIndex + (1 shl k)
|
nodeIndex + (1 shl k)
|
||||||
} else {
|
} else {
|
||||||
// Node is a right child
|
|
||||||
nodeIndex - (1 shl k)
|
nodeIndex - (1 shl k)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (p < nodeCount) {
|
return if (p < nodeCount) {
|
||||||
p
|
p
|
||||||
} else {
|
} else {
|
||||||
// If parent is out of range, we're at the edge of a non-full tree
|
// Parent is out of range. This node is at the rightmost
|
||||||
// Go up further by recursing
|
// edge of a non-full tree. Walk up through virtual parents
|
||||||
parent(p, nodeCount)
|
// until we find one within range.
|
||||||
|
parentInRange(p, nodeCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parentInRange(
|
||||||
|
nodeIndex: Int,
|
||||||
|
nodeCount: Int,
|
||||||
|
): Int {
|
||||||
|
val k = level(nodeIndex)
|
||||||
|
val b = (nodeIndex shr (k + 1)) and 1
|
||||||
|
val p =
|
||||||
|
if (b == 0) {
|
||||||
|
nodeIndex + (1 shl k)
|
||||||
|
} else {
|
||||||
|
nodeIndex - (1 shl k)
|
||||||
|
}
|
||||||
|
return if (p < nodeCount) p else parentInRange(p, nodeCount)
|
||||||
|
}
|
||||||
|
|
||||||
/** Root node index for a tree with [leafCount] leaves */
|
/** Root node index for a tree with [leafCount] leaves */
|
||||||
fun root(leafCount: Int): Int {
|
fun root(leafCount: Int): Int {
|
||||||
if (leafCount <= 1) return 0
|
if (leafCount <= 1) return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user