From 9d0a0bc6b3ce66af214b08aef85f4a9a8286197e Mon Sep 17 00:00:00 2001 From: souma4 Date: Sun, 23 Feb 2025 14:41:45 -0700 Subject: [PATCH 1/7] Add utility functions to traverse trees to find minimum, maximum nodes within the tree and within subtrees, and an abovebelow function that returns the nodes immediately above and below a given BinaryNode --- src/binarytree.jl | 64 +++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 23 ++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/binarytree.jl b/src/binarytree.jl index d5b40da..dae9d46 100644 --- a/src/binarytree.jl +++ b/src/binarytree.jl @@ -143,3 +143,67 @@ function _printkeyvalue(io::IO, node::BinaryNode) show(ioctx, val) end end + +# ----------- +# UTILITIES +# ----------- + +minnode(tree::BinaryTree) = minnode(root(tree)) + +function minnode(node::BinaryNode) + leftnode = left(node) + isnothing(leftnode) ? node : minnode(leftnode) +end + +minnode(node::Nothing) = nothing + +maxnode(tree::BinaryTree) = maxnode(root(tree)) + +function maxnode(node::BinaryNode) + rightnode = right(node) + isnothing(rightnode) ? node : maxnode(rightnode) +end + +maxnode(node::Nothing) = nothing + +function abovebelow(tree::BinaryNode, x::BinaryNode) + above, below = nothing, nothing + current = tree + # Traverse from the root to the target node, updating candidates. + while !isnothing(current) && key(current) != key(x) + if key(x) < key(current) + # current is a potential above (successor) + above = current + current = left(current) + else # x.key > current.key + # current is a potential below (predecessor) + below = current + current = right(current) + end + end + + # If the node wasn't found, return the best candidate values + if isnothing(current) + return (above, below) + end + + # Found the node with key equal to x.key. + # Now, if there is a left subtree, the true below (predecessor) is the maximum in that subtree. + if !isnothing(left(current)) + below = maxnode(left(current)) + end + # Similarly, if there is a right subtree, the true above (successor) is the minimum in that subtree. + if !isnothing(right(current)) + above = minnode(right(current)) + end + + (above, below) +end + +function abovebelow(tree::BinaryTree, x::BinaryNode) + abovebelow(root(tree), x) +end + +function abovebelow(tree, x::Nothing) + (nothing, nothing) +end diff --git a/test/runtests.jl b/test/runtests.jl index fc18830..31eba02 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -93,7 +93,7 @@ const BT = BinaryTrees BT.insert!(tree, 2, 20) BT.insert!(tree, 1, 10) BT.insert!(tree, 3, 30) - # deleting a key that does not exist + # deleting a key that does not exist # does not change the tree BT.delete!(tree, 4) @test tree === tree @@ -219,11 +219,32 @@ const BT = BinaryTrees @test BT.value(BT.search(tree, (0, 0, 1))) == 1 @test BT.value(BT.search(tree, (1, 0, 0))) == 3 + # traversal algorithms + tree = AVLTree{Int,Float64}() + BT.insert!(tree, 0, 5) + BT.insert!(tree, 1, 6) + BT.insert!(tree, 2, 8) + BT.insert!(tree, 3, 10) + BT.insert!(tree, 4, 20) + BT.insert!(tree, 5, 30) + BT.insert!(tree, 6, 40) + @test BT.key(BT.minnode(tree)) == 0 + @test BT.key(BT.maxnode(tree)) == 6 + @test BT.abovebelow(tree, BT.AVLNode(0, 5))[2] == nothing + @test BT.key.(BT.abovebelow(tree, BT.AVLNode(2, 10))) == (3, 1) + @test BT.key.(BT.abovebelow(BT.root(tree), BT.AVLNode(2, 10))) == (3, 1) + @test BT.key.(BT.abovebelow(tree, BT.AVLNode(5, 30))) == (6, 4) + @test BT.abovebelow(tree, nothing) == (nothing, nothing) + @test BT.abovebelow(BT.root(tree), nothing) == (nothing, nothing) + # type stability tree = AVLTree{Int,Int}() @inferred BT.insert!(tree, 2, 20) @inferred BT.insert!(tree, 1, 10) @inferred BT.insert!(tree, 3, 30) + @inferred BT.minnode(tree) + @inferred BT.maxnode(tree) + @inferred BT.abovebelow(tree, BT.AVLNode(2, 20)) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) From f05f43f328edb1ae16dd60ca4b13501787da1162 Mon Sep 17 00:00:00 2001 From: souma4 Date: Sun, 23 Feb 2025 22:33:01 -0700 Subject: [PATCH 2/7] updating abovebelow to prevnext to be more generally descriptive. Also accepts the key, functions like an advanced search now. fixed tests using @inferred Nothing. --- src/binarytree.jl | 38 +++++++++++++++++--------------------- test/runtests.jl | 21 ++++++++++++--------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/binarytree.jl b/src/binarytree.jl index dae9d46..e059cd5 100644 --- a/src/binarytree.jl +++ b/src/binarytree.jl @@ -166,44 +166,40 @@ end maxnode(node::Nothing) = nothing -function abovebelow(tree::BinaryNode, x::BinaryNode) - above, below = nothing, nothing - current = tree +function prevnext(tree::BinaryTree, k) + prev, next = nothing, nothing + current = root(tree) # Traverse from the root to the target node, updating candidates. - while !isnothing(current) && key(current) != key(x) - if key(x) < key(current) - # current is a potential above (successor) - above = current + while !isnothing(current) && key(current) != k + if k < key(current) + # current is a potential next (successor) + next = current current = left(current) - else # x.key > current.key - # current is a potential below (predecessor) - below = current + else # k.key > current.key + # current is a potential previous (predecessor) + prev = current current = right(current) end end # If the node wasn't found, return the best candidate values if isnothing(current) - return (above, below) + return (prev, next) end # Found the node with key equal to x.key. - # Now, if there is a left subtree, the true below (predecessor) is the maximum in that subtree. + # Now, if there is a left subtree, the true previous (predecessor) is the maximum in that subtree. if !isnothing(left(current)) - below = maxnode(left(current)) + prev = maxnode(left(current)) end - # Similarly, if there is a right subtree, the true above (successor) is the minimum in that subtree. + # Similarly, if there is a right subtree, the true next (successor) is the minimum in that subtree. if !isnothing(right(current)) - above = minnode(right(current)) + next = minnode(right(current)) end - (above, below) + (prev, next) end -function abovebelow(tree::BinaryTree, x::BinaryNode) - abovebelow(root(tree), x) -end - -function abovebelow(tree, x::Nothing) +function prevnext(tree::BinaryTree, k::Nothing) (nothing, nothing) end diff --git a/test/runtests.jl b/test/runtests.jl index 31eba02..e142db9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -230,21 +230,18 @@ const BT = BinaryTrees BT.insert!(tree, 6, 40) @test BT.key(BT.minnode(tree)) == 0 @test BT.key(BT.maxnode(tree)) == 6 - @test BT.abovebelow(tree, BT.AVLNode(0, 5))[2] == nothing - @test BT.key.(BT.abovebelow(tree, BT.AVLNode(2, 10))) == (3, 1) - @test BT.key.(BT.abovebelow(BT.root(tree), BT.AVLNode(2, 10))) == (3, 1) - @test BT.key.(BT.abovebelow(tree, BT.AVLNode(5, 30))) == (6, 4) - @test BT.abovebelow(tree, nothing) == (nothing, nothing) - @test BT.abovebelow(BT.root(tree), nothing) == (nothing, nothing) + @test BT.prevnext(tree, 0)[1] == nothing + @test BT.key.(BT.prevnext(tree, 2)) == (1, 3) + @test BT.key.(BT.prevnext(tree, 5)) == (4, 6) + @test BT.prevnext(tree, nothing) == (nothing, nothing) # type stability tree = AVLTree{Int,Int}() @inferred BT.insert!(tree, 2, 20) @inferred BT.insert!(tree, 1, 10) @inferred BT.insert!(tree, 3, 30) - @inferred BT.minnode(tree) - @inferred BT.maxnode(tree) - @inferred BT.abovebelow(tree, BT.AVLNode(2, 20)) + @inferred Nothing BT.minnode(tree) + @inferred Nothing BT.maxnode(tree) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @@ -255,6 +252,8 @@ const BT = BinaryTrees @inferred BT.insert!(tree, 2) @inferred BT.insert!(tree, 1) @inferred BT.insert!(tree, 3) + @inferred Nothing BT.minnode(tree) + @inferred Nothing BT.maxnode(tree) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @@ -265,6 +264,8 @@ const BT = BinaryTrees @inferred BT.insert!(tree, "key2", 2) @inferred BT.insert!(tree, "key1", 1) @inferred BT.insert!(tree, "key3", 3) + @inferred Nothing BT.minnode(tree) + @inferred Nothing BT.maxnode(tree) @inferred Nothing BT.search(tree, "key2") @inferred Nothing BT.search(tree, "key1") @inferred Nothing BT.search(tree, "key3") @@ -275,6 +276,8 @@ const BT = BinaryTrees @inferred BT.insert!(tree, (0, 1, 0), 2) @inferred BT.insert!(tree, (0, 0, 1), 1) @inferred BT.insert!(tree, (1, 0, 0), 3) + @inferred Nothing BT.minnode(tree) + @inferred Nothing BT.maxnode(tree) @inferred Nothing BT.search(tree, (0, 1, 0)) @inferred Nothing BT.search(tree, (0, 0, 1)) @inferred Nothing BT.search(tree, (1, 0, 0)) From 877367217bc85cb1387ce4c7e1ccf47108424754 Mon Sep 17 00:00:00 2001 From: souma4 Date: Mon, 24 Feb 2025 08:54:41 -0700 Subject: [PATCH 3/7] inlined prevnext with nothing input, and edited comments to meet style. --- src/binarytree.jl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/binarytree.jl b/src/binarytree.jl index e059cd5..e5ccd28 100644 --- a/src/binarytree.jl +++ b/src/binarytree.jl @@ -169,30 +169,29 @@ maxnode(node::Nothing) = nothing function prevnext(tree::BinaryTree, k) prev, next = nothing, nothing current = root(tree) - # Traverse from the root to the target node, updating candidates. + # traverse from the root to the target node, updating candidates while !isnothing(current) && key(current) != k if k < key(current) # current is a potential next (successor) next = current current = left(current) - else # k.key > current.key + else # k > key(current) # current is a potential previous (predecessor) prev = current current = right(current) end end - # If the node wasn't found, return the best candidate values + # if the node wasn't found, return the best candidate values if isnothing(current) return (prev, next) end - # Found the node with key equal to x.key. - # Now, if there is a left subtree, the true previous (predecessor) is the maximum in that subtree. + # if there is a left subtree, the true previous (predecessor) is the maximum in that subtree if !isnothing(left(current)) prev = maxnode(left(current)) end - # Similarly, if there is a right subtree, the true next (successor) is the minimum in that subtree. + # similarly, if there is a right subtree, the true next (successor) is the minimum in that subtree if !isnothing(right(current)) next = minnode(right(current)) end @@ -200,6 +199,4 @@ function prevnext(tree::BinaryTree, k) (prev, next) end -function prevnext(tree::BinaryTree, k::Nothing) - (nothing, nothing) -end +prevnext(tree::BinaryTree, k::Nothing) = (nothing, nothing) From 2ce4f40ff81dde35f2aef6b94e52def9a8428f46 Mon Sep 17 00:00:00 2001 From: souma4 Date: Mon, 24 Feb 2025 14:48:52 -0700 Subject: [PATCH 4/7] added @inferred check for BT.prevnext methods. I chose to set AllowedType to the full type (using their subset rule). Let me know if there's a better alternative --- test/runtests.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index e142db9..b894a09 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -242,6 +242,11 @@ const BT = BinaryTrees @inferred BT.insert!(tree, 3, 30) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) + @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( + tree, + 2 + ) + @inferred BT.prevnext(tree, nothing) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @@ -254,6 +259,10 @@ const BT = BinaryTrees @inferred BT.insert!(tree, 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) + @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( + tree, + 2 + ) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @@ -266,6 +275,10 @@ const BT = BinaryTrees @inferred BT.insert!(tree, "key3", 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) + @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( + tree, + "key2" + ) @inferred Nothing BT.search(tree, "key2") @inferred Nothing BT.search(tree, "key1") @inferred Nothing BT.search(tree, "key3") @@ -278,6 +291,10 @@ const BT = BinaryTrees @inferred BT.insert!(tree, (1, 0, 0), 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) + @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( + tree, + (0, 1, 0) + ) @inferred Nothing BT.search(tree, (0, 1, 0)) @inferred Nothing BT.search(tree, (0, 0, 1)) @inferred Nothing BT.search(tree, (1, 0, 0)) From c1ac4c045ec377b1f9926cac5cdbaa62c9ddb932 Mon Sep 17 00:00:00 2001 From: souma4 Date: Tue, 25 Feb 2025 10:12:18 -0700 Subject: [PATCH 5/7] switched inferred checks for prevnext to index into results rather than allow supertype of result --- test/runtests.jl | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index b894a09..b513d7c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -242,10 +242,8 @@ const BT = BinaryTrees @inferred BT.insert!(tree, 3, 30) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) - @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( - tree, - 2 - ) + @inferred Nothing BT.prevnext(tree, 2)[1] + @inferred Nothing BT.prevnext(tree, 2)[2] @inferred BT.prevnext(tree, nothing) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @@ -259,10 +257,10 @@ const BT = BinaryTrees @inferred BT.insert!(tree, 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) - @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( - tree, - 2 - ) + @inferred Nothing BT.prevnext(tree, 2)[1] + @inferred Nothing BT.prevnext(tree, 2)[2] + @inferred BT.prevnext(tree, nothing) + @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @@ -275,10 +273,9 @@ const BT = BinaryTrees @inferred BT.insert!(tree, "key3", 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) - @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( - tree, - "key2" - ) + @inferred Nothing BT.prevnext(tree, "key2")[1] + @inferred Nothing BT.prevnext(tree, "key2")[2] + @inferred BT.prevnext(tree, nothing) @inferred Nothing BT.search(tree, "key2") @inferred Nothing BT.search(tree, "key1") @inferred Nothing BT.search(tree, "key3") @@ -291,10 +288,9 @@ const BT = BinaryTrees @inferred BT.insert!(tree, (1, 0, 0), 3) @inferred Nothing BT.minnode(tree) @inferred Nothing BT.maxnode(tree) - @inferred Tuple{Union{BT.AVLNode,Nothing},Union{BT.AVLNode,Nothing}} BT.prevnext( - tree, - (0, 1, 0) - ) + @inferred Nothing BT.prevnext(tree, (0, 1, 0))[1] + @inferred Nothing BT.prevnext(tree, (0, 1, 0))[2] + @inferred BT.prevnext(tree, nothing) @inferred Nothing BT.search(tree, (0, 1, 0)) @inferred Nothing BT.search(tree, (0, 0, 1)) @inferred Nothing BT.search(tree, (1, 0, 0)) From da7f4e87b32028b8e48e134531cd7feacb75a989 Mon Sep 17 00:00:00 2001 From: souma4 Date: Tue, 25 Feb 2025 22:42:01 -0700 Subject: [PATCH 6/7] removed test typo and added docstrings for new utility functions --- src/binarytree.jl | 29 +++++++++++++++++++++++++++++ test/runtests.jl | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/binarytree.jl b/src/binarytree.jl index e5ccd28..3bb1872 100644 --- a/src/binarytree.jl +++ b/src/binarytree.jl @@ -148,6 +148,16 @@ end # UTILITIES # ----------- +""" + BinaryTrees.minnode(tree) + +Find the `node` with the smallest `key` in the `tree`. + + BinaryTrees.minnode(node) + +Find the `node` with the smallest `key` in the subtree rooted at `node`. +If `nothing` is provided, `nothing` is returned. +""" minnode(tree::BinaryTree) = minnode(root(tree)) function minnode(node::BinaryNode) @@ -157,6 +167,16 @@ end minnode(node::Nothing) = nothing +""" + BinaryTrees.maxnode(tree) + +Find the `node` with the maximum `key` in the `tree`. + + BinaryTrees.maxnode(node) + +Find the `node` with the maximum `key` in the subtree rooted at `node`. +If `nothing` is provided, `nothing` is returned. +""" maxnode(tree::BinaryTree) = maxnode(root(tree)) function maxnode(node::BinaryNode) @@ -166,6 +186,15 @@ end maxnode(node::Nothing) = nothing +""" + BinaryTrees.prevnext(tree, k) + +Returns a `tuple` of each `node` immediately before +and after the `node` with `key`, `k` within `tree`. + +If an adjacent `node` does not exist, `nothing` is returned in its place. +If `k` is `nothing`, returns `(nothing, nothing)`. +""" function prevnext(tree::BinaryTree, k) prev, next = nothing, nothing current = root(tree) diff --git a/test/runtests.jl b/test/runtests.jl index b513d7c..4732f6b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -261,7 +261,6 @@ const BT = BinaryTrees @inferred Nothing BT.prevnext(tree, 2)[2] @inferred BT.prevnext(tree, nothing) @inferred Nothing BT.search(tree, 2) - @inferred Nothing BT.search(tree, 2) @inferred Nothing BT.search(tree, 1) @inferred Nothing BT.search(tree, 3) @inferred BT.delete!(tree, 2) From 1e9a2920d4624f08fd80f4dfaf37f2cbe993778c Mon Sep 17 00:00:00 2001 From: souma4 Date: Wed, 26 Feb 2025 14:29:35 -0700 Subject: [PATCH 7/7] Updated docstrings to match style and squashed commits from that process (mybad) --- src/binarytree.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/binarytree.jl b/src/binarytree.jl index 3bb1872..6e0c240 100644 --- a/src/binarytree.jl +++ b/src/binarytree.jl @@ -151,11 +151,11 @@ end """ BinaryTrees.minnode(tree) -Find the `node` with the smallest `key` in the `tree`. +Find the node with the smallest key in the `tree`. BinaryTrees.minnode(node) -Find the `node` with the smallest `key` in the subtree rooted at `node`. +Find the node with the smallest key in the subtree rooted at `node`. If `nothing` is provided, `nothing` is returned. """ minnode(tree::BinaryTree) = minnode(root(tree)) @@ -170,11 +170,11 @@ minnode(node::Nothing) = nothing """ BinaryTrees.maxnode(tree) -Find the `node` with the maximum `key` in the `tree`. +Find the node with the maximum key in the `tree`. BinaryTrees.maxnode(node) -Find the `node` with the maximum `key` in the subtree rooted at `node`. +Find the node with the maximum key in the subtree rooted at `node`. If `nothing` is provided, `nothing` is returned. """ maxnode(tree::BinaryTree) = maxnode(root(tree)) @@ -189,10 +189,10 @@ maxnode(node::Nothing) = nothing """ BinaryTrees.prevnext(tree, k) -Returns a `tuple` of each `node` immediately before -and after the `node` with `key`, `k` within `tree`. +Returns a tuple of each node immediately before +and after the `tree` node with key `k`. -If an adjacent `node` does not exist, `nothing` is returned in its place. +If an adjacent node does not exist, `nothing` is returned in its place. If `k` is `nothing`, returns `(nothing, nothing)`. """ function prevnext(tree::BinaryTree, k)