Skip to content

Commit 0e2ae9d

Browse files
authored
Merge pull request #41 from JuliaCollections/teh/more_examples
Add binary tree examples
2 parents 04a6f8f + 91c0542 commit 0e2ae9d

7 files changed

+197
-27
lines changed

examples/binarytree_core.jl

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using AbstractTrees
2+
3+
mutable struct BinaryNode{T}
4+
data::T
5+
parent::BinaryNode{T}
6+
left::BinaryNode{T}
7+
right::BinaryNode{T}
8+
9+
# Root constructor
10+
BinaryNode{T}(data) where T = new{T}(data)
11+
# Child node constructor
12+
BinaryNode{T}(data, parent::BinaryNode{T}) where T = new{T}(data, parent)
13+
end
14+
BinaryNode(data) = BinaryNode{typeof(data)}(data)
15+
16+
function leftchild(data, parent::BinaryNode)
17+
!isdefined(parent, :left) || error("left child is already assigned")
18+
node = typeof(parent)(data, parent)
19+
parent.left = node
20+
end
21+
function rightchild(data, parent::BinaryNode)
22+
!isdefined(parent, :right) || error("right child is already assigned")
23+
node = typeof(parent)(data, parent)
24+
parent.right = node
25+
end

examples/binarytree_easy.jl

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# This file demonstrates a relatively simple way to implement the AbstractTrees
2+
# interface for a classic binary tree. See "binarytree_infer.jl" for a more performant
3+
# (but laborious) approach.
4+
5+
if !isdefined(@__MODULE__, :BinaryNode)
6+
include("binarytree_core.jl")
7+
end
8+
9+
## Things we need to define
10+
function AbstractTrees.children(node::BinaryNode)
11+
if isdefined(node, :left)
12+
if isdefined(node, :right)
13+
return (node.left, node.right)
14+
end
15+
return (node.left,)
16+
end
17+
isdefined(node, :right) && return (node.right,)
18+
return ()
19+
end
20+
21+
## Things that make printing prettier
22+
AbstractTrees.printnode(io::IO, node::BinaryNode) = print(io, node.data)
23+
24+
## Optional enhancements
25+
# These next two definitions allow inference of the item type in iteration.
26+
# (They are not sufficient to solve all internal inference issues, however.)
27+
Base.eltype(::Type{<:TreeIterator{BinaryNode{T}}}) where T = BinaryNode{T}
28+
Base.IteratorEltype(::Type{<:TreeIterator{BinaryNode{T}}}) where T = Base.HasEltype()
29+
30+
## Let's test it. First build a tree.
31+
root = BinaryNode(0)
32+
l = leftchild(1, root)
33+
r = rightchild(2, root)
34+
lr = rightchild(3, l)
35+
36+
print_tree(root)
37+
collect(PostOrderDFS(root))
38+
@static if isdefined(@__MODULE__, :Test)
39+
@testset "binarytree_easy.jl" begin
40+
@test [node.data for node in PostOrderDFS(root)] == [3, 1, 2, 0]
41+
@test [node.data for node in PreOrderDFS(root)] == [0, 1, 3, 2]
42+
@test [node.data for node in Leaves(root)] == [3, 2]
43+
end
44+
end

examples/binarytree_infer.jl

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This file illustrates how to create inferrable tree-iteration methods in circumstances
2+
# where the children are not naturally indexable.
3+
# See "binarytree_easy.jl" for a simpler approach.
4+
5+
if !isdefined(@__MODULE__, :BinaryNode)
6+
include("binarytree_core.jl")
7+
end
8+
9+
## Enhancement of the "native" binary tree
10+
# You might have the methods below even if you weren't trying to support AbstractTrees.
11+
12+
# Implement iteration over the immediate children of a node
13+
function Base.iterate(node::BinaryNode)
14+
isdefined(node, :left) && return (node.left, false)
15+
isdefined(node, :right) && return (node.right, true)
16+
return nothing
17+
end
18+
function Base.iterate(node::BinaryNode, state::Bool)
19+
state && return nothing
20+
isdefined(node, :right) && return (node.right, true)
21+
return nothing
22+
end
23+
Base.IteratorSize(::Type{BinaryNode{T}}) where T = Base.SizeUnknown()
24+
Base.eltype(::Type{BinaryNode{T}}) where T = BinaryNode{T}
25+
26+
## Things we need to define to leverage the native iterator over children
27+
## for the purposes of AbstractTrees.
28+
# Set the traits of this kind of tree
29+
Base.eltype(::Type{<:TreeIterator{BinaryNode{T}}}) where T = BinaryNode{T}
30+
Base.IteratorEltype(::Type{<:TreeIterator{BinaryNode{T}}}) where T = Base.HasEltype()
31+
AbstractTrees.parentlinks(::Type{BinaryNode{T}}) where T = AbstractTrees.StoredParents()
32+
AbstractTrees.siblinglinks(::Type{BinaryNode{T}}) where T = AbstractTrees.StoredSiblings()
33+
# Use the native iteration for the children
34+
AbstractTrees.children(node::BinaryNode) = node
35+
36+
Base.parent(root::BinaryNode, node::BinaryNode) = isdefined(node, :parent) ? node.parent : nothing
37+
38+
function AbstractTrees.nextsibling(tree::BinaryNode, child::BinaryNode)
39+
isdefined(child, :parent) || return nothing
40+
p = child.parent
41+
if isdefined(p, :right)
42+
child === p.right && return nothing
43+
return p.right
44+
end
45+
return nothing
46+
end
47+
48+
# We also need `pairs` to return something sensible.
49+
# If you don't like integer keys, you could do, e.g.,
50+
# Base.pairs(node::BinaryNode) = BinaryNodePairs(node)
51+
# and have its iteration return, e.g., `:left=>node.left` and `:right=>node.right` when defined.
52+
# But the following is easy:
53+
Base.pairs(node::BinaryNode) = enumerate(node)
54+
55+
56+
AbstractTrees.printnode(io::IO, node::BinaryNode) = print(io, node.data)
57+
58+
root = BinaryNode(0)
59+
l = leftchild(1, root)
60+
r = rightchild(2, root)
61+
lr = rightchild(3, l)
62+
63+
print_tree(root)
64+
collect(PostOrderDFS(root))
65+
@static if isdefined(@__MODULE__, :Test)
66+
@testset "binarytree_infer.jl" begin
67+
@test @inferred(map(x->x.data, PostOrderDFS(root))) == [3, 1, 2, 0]
68+
@test @inferred(map(x->x.data, PreOrderDFS(root))) == [0, 1, 3, 2]
69+
@test @inferred(map(x->x.data, Leaves(root))) == [3, 2]
70+
end
71+
end

examples/fstree.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ end
2424
printnode(io::IO, d::Directory) = print(io, basename(d.path))
2525
printnode(io::IO, f::File) = print(io, basename(f.path))
2626

27-
dirpath = realpath(joinpath(dirname(pathof(AbstractTrees)),".."))
27+
dirpath = realpath(dirname(@__DIR__))
2828
d = Directory(dirpath)
2929
print_tree(d)

src/AbstractTrees.jl

+14-16
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ include("implicitstacks.jl")
2323
children(x)
2424
2525
Return the immediate children of node `x`. You should specialize this method
26-
for custom tree structures.
26+
for custom tree structures. It should return an iterable object for which an
27+
appropriate implementation of `Base.pairs` is available.
2728
2829
# Example
2930
@@ -201,7 +202,7 @@ show(io::IO, tree::Tree) = print_tree(io, tree.x)
201202

202203
mutable struct AnnotationNode{T}
203204
val::T
204-
children::Array{AnnotationNode{T}}
205+
children::Vector{AnnotationNode{T}}
205206
end
206207

207208
children(x::AnnotationNode) = x.children
@@ -317,12 +318,11 @@ Any[1,Any[2,3]]
317318
318319
we will get [1,2,3,Any[2,3],Any[1,Any[2,3]]]
319320
"""
320-
struct PostOrderDFS <: TreeIterator{Any}
321-
tree::Any
322-
PostOrderDFS(x::Any) = new(x)
321+
struct PostOrderDFS{T} <: TreeIterator{T}
322+
tree::T
323323
end
324324
PostOrderDFS(tree::Tree) = PostOrderDFS(tree.x)
325-
IteratorSize(::Type{PostOrderDFS}) = SizeUnknown()
325+
IteratorSize(::Type{PostOrderDFS{T}}) where T = SizeUnknown()
326326

327327
"""
328328
Iterator to visit the nodes of a tree, guaranteeing that parents
@@ -378,16 +378,8 @@ parentstate(tree, state) = parentstate(tree, state, treekind(tree))
378378
update_state!(old_state, cs, idx) = next(cs, idx)[1]
379379

380380

381-
struct ImplicitRootState
382-
end
383381
getindex(x::AbstractArray, ::ImplicitRootState) = x
384382

385-
"""
386-
Trees must override with method if the state of the root is not the same as the
387-
tree itself (e.g. IndexedTrees should always override this method).
388-
"""
389-
rootstate(x) = ImplicitRootState()
390-
391383
function firststate(ti::PreOrderDFS{T}) where T
392384
if isa(parentlinks(ti.tree), StoredParents) &&
393385
isa(siblinglinks(ti.tree), SiblingLinks)
@@ -415,6 +407,7 @@ relative_state(tree, parentstate, childstate::ImplicitIndexStack) =
415407
childstate.stack[end]
416408
relative_state(tree, parentstate, childstate::ImplicitNodeStack) =
417409
relative_state(tree, parentstate, childstate.idx_stack)
410+
418411
function nextsibling(tree, state)
419412
ps = parentstate(tree, state)
420413
cs = childstates(tree, ps)
@@ -435,7 +428,7 @@ function nextsibling(node, ::StoredParents, ::ImplicitSiblings, ::RegularTree)
435428
last_was_node && return nothing
436429
error("Tree inconsistency: node not a child of parent")
437430
end
438-
nextsibling(node, ::Any, ::StoredSiblings, ::Any) = error("Trees with explicit siblings must override the `prevsibling` method explicitly")
431+
nextsibling(node, ::Any, ::StoredSiblings, ::Any) = error("Trees with explicit siblings must override the `nextsibling` method explicitly")
439432
nextsibling(node) = nextsibling(node, parentlinks(node), siblinglinks(node), treekind(node))
440433

441434
function prevsibling(node, ::StoredParents, ::ImplicitSiblings, ::RegularTree)
@@ -465,6 +458,8 @@ end
465458
children(tree::Subtree) = children(tree.tree, tree.state)
466459
nodetype(tree::Subtree) = nodetype(tree.tree)
467460
idxtype(tree::Subtree) = idxtype(tree.tree)
461+
rootstate(tree::Subtree) = tree.state
462+
parentlinks(::Type{Subtree{T,S}}) where {T,S} = parentlinks(T)
468463

469464
joinstate(tree, a, b) = b
470465

@@ -495,8 +490,11 @@ function stepstate(ti::TreeIterator, state)
495490
nothing
496491
end
497492

498-
getnode(tree, ns) = isa(treekind(tree), IndexedTree) ? tree[ns] : ns
499493
getnode(tree::AbstractShadowTree, ns::ImplicitNodeStack) = tree[ns.idx_stack.stack]
494+
getnode(tree, ns) = getnode(tree, ns, treekind(tree))
495+
getnode(tree, ns, ::IndexedTree) = tree[ns]
496+
getnode(tree, ns, ::RegularTree) = ns
497+
getnode(tree, ::ImplicitRootState, ::RegularTree) = tree
500498

501499
function iterate(ti::TreeIterator)
502500
state = firststate(ti)

src/traits.jl

+12-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ struct ImplicitSiblings <: SiblingLinks; end
2626
siblinglinks(::Type) = ImplicitSiblings()
2727
siblinglinks(tree) = siblinglinks(typeof(tree))
2828

29+
struct ImplicitRootState
30+
end
31+
32+
"""
33+
state = rootstate(tree)
34+
35+
Trees must override with method if the state of the root is not the same as the
36+
tree itself (e.g. IndexedTrees should always override this method).
37+
"""
38+
rootstate(x) = ImplicitRootState()
39+
2940

3041
abstract type TreeKind end
3142
struct RegularTree <: TreeKind; end
@@ -34,12 +45,10 @@ struct IndexedTree <: TreeKind; end
3445
treekind(tree::Type) = RegularTree()
3546
treekind(tree) = treekind(typeof(tree))
3647
children(tree, node, ::RegularTree) = children(node)
48+
children(tree, ::ImplicitRootState, ::RegularTree) = children(tree)
3749
children(tree, node, ::IndexedTree) = (tree[y] for y in childindices(tree, node))
3850
children(tree, node) = children(tree, node, treekind(tree))
3951

40-
function rootstate()
41-
end
42-
4352
childindices(tree, node) =
4453
tree == node ? childindices(tree, rootstate(tree)) :
4554
error("Must implement childindices(tree, node)")

test/runtests.jl

+30-7
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ using AbstractTrees
22
using Test
33
import Base: ==
44

5-
if !isdefined(@__MODULE__, :isfirstrun)
6-
const isfirstrun = Ref(true)
7-
end
8-
if isfirstrun[] && VERSION >= v"1.1.0-DEV.838" # requires https://github.com/JuliaLang/julia/pull/30291
9-
@test isempty(detect_ambiguities(AbstractTrees, Base, Core))
10-
isfirstrun[] = false
5+
if VERSION >= v"1.1.0-DEV.838" # requires https://github.com/JuliaLang/julia/pull/30291
6+
@testset "Ambiguities" begin
7+
@test isempty(detect_ambiguities(AbstractTrees, Base, Core))
8+
end
119
end
1210

1311
AbstractTrees.children(x::Array) = x
1412
tree = Any[1,Any[2,3]]
1513

14+
@testset "Array" begin
1615
io = IOBuffer()
1716
print_tree(io, tree)
1817
@test String(take!(io)) == "Array{Any,1}\n├─ 1\n└─ Array{Any,1}\n ├─ 2\n └─ 3\n"
@@ -23,6 +22,7 @@ print_tree(io, tree)
2322

2423
tree2 = Any[Any[1,2],Any[3,4]]
2524
@test collect(PreOrderDFS(tree2)) == Any[tree2,Any[1,2],1,2,Any[3,4],3,4]
25+
end
2626

2727
"""
2828
A tree in which every node has 0 or 1 children
@@ -44,12 +44,16 @@ Base.eltype(::Type{<:TreeIterator{OneTree}}) = Int
4444
Base.IteratorEltype(::Type{<:TreeIterator{OneTree}}) = Base.HasEltype()
4545

4646
ot = OneTree([2,3,4,0])
47+
48+
@testset "OneTree" begin
49+
io = IOBuffer()
4750
print_tree(io, ot)
4851
@test String(take!(io)) == "2\n└─ 3\n └─ 4\n └─ 0\n"
4952
@test @inferred(collect(Leaves(ot))) == [0]
5053
@test eltype(collect(Leaves(ot))) === Int
5154
@test collect(PreOrderDFS(ot)) == [2,3,4,0]
5255
@test collect(PostOrderDFS(ot)) == [0,4,3,2]
56+
end
5357

5458
"""
5559
Stores an explicit parent for some other kind of tree
@@ -72,6 +76,8 @@ AbstractTrees.printnode(io::IO, t::ParentTree) =
7276
AbstractTrees.printnode(io::IO, t[AbstractTrees.rootstate(t)])
7377

7478
pt = ParentTree(ot,[0,1,2,3])
79+
@testset "ParentTree" begin
80+
io = IOBuffer()
7581
print_tree(io, pt)
7682
@test String(take!(io)) == "2\n└─ 3\n └─ 4\n └─ 0\n"
7783
@test collect(Leaves(pt)) == [0]
@@ -88,7 +94,8 @@ b = treemap!(PreOrderDFS(a)) do node
8894
empty!(node)
8995
ret
9096
end
91-
@assert b == Any[0,1,Any[0,2,[0,3]]]
97+
@test b == Any[0,1,Any[0,2,[0,3]]]
98+
end
9299

93100
struct IntTree
94101
num::Int
@@ -102,10 +109,12 @@ Base.eltype(::Type{<:TreeIterator{IntTree}}) = IntTree
102109
Base.IteratorEltype(::Type{<:TreeIterator{IntTree}}) = Base.HasEltype()
103110
AbstractTrees.nodetype(::IntTree) = IntTree
104111
iter = Leaves(itree)
112+
@testset "IntTree" begin
105113
@test @inferred(first(iter)) == IntTree(2, IntTree[])
106114
val, state = iterate(iter)
107115
@test Base.return_types(iterate, Tuple{typeof(iter), typeof(state)}) ==
108116
[Union{Nothing, Tuple{IntTree,typeof(state)}}]
117+
end
109118

110119
#=
111120
@test treemap(PostOrderDFS(tree)) do ind, x, children
@@ -115,3 +124,17 @@ end == IntTree(6,[IntTree(1,IntTree[]),IntTree(5,[IntTree(2,IntTree[]),IntTree(3
115124
=#
116125

117126
@test collect(PostOrderDFS([])) == Any[[]]
127+
128+
@testset "Examples" begin
129+
# Ensure the examples run
130+
exampledir = joinpath(dirname(@__DIR__), "examples")
131+
examples = readdir(exampledir)
132+
mktemp() do filename, io
133+
redirect_stdout(io) do
134+
for ex in examples
135+
haskey(ENV, "CI") && Sys.isapple() && ex == "fstree.jl" && continue
136+
include(joinpath(exampledir, ex))
137+
end
138+
end
139+
end
140+
end # @testset "Examples"

0 commit comments

Comments
 (0)