Skip to content

Commit 486305f

Browse files
committed
treemap now allows for modification of children
1 parent 5c22919 commit 486305f

File tree

2 files changed

+43
-30
lines changed

2 files changed

+43
-30
lines changed

src/iteration.jl

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ function Base.iterate(ti::StatelessBFS, ind)
412412
end
413413

414414

415-
#TODO: still experimenting here
416415
"""
417416
MapNode{T,C}
418417
@@ -428,13 +427,13 @@ struct MapNode{T,C}
428427
value::T
429428
children::C
430429

431-
#TODO: need a way to modify children but hard to think of a good general way of doing it...
432-
function MapNode(𝒻, node)
433-
v = 𝒻(node)
434-
ch = map(c -> MapNode(𝒻, c), children(node))
435-
new{typeof(v),typeof(ch)}(v, ch)
430+
function MapNode(f, node)
431+
(v, ch) = f(node)
432+
ch′ = map(c -> MapNode(f, c), ch)
433+
new{typeof(v),typeof(ch′)}(v, ch′)
436434
end
437435
end
436+
MapNode(node) = MapNode(n -> (nodevalue(n), children(n)), nodevalue(node))
438437

439438
children::MapNode) = μ.children
440439

@@ -452,31 +451,51 @@ end
452451
Base.show(io::IO, ::MIME"text/plain", μ::MapNode) = print_tree(io, μ)
453452

454453

455-
#TODO: still experimenting here
456454
"""
457455
treemap(f, node)
458456
459-
Apply the function `f` to every node in the tree with root `node`. `node` must satisfy the AbstractTrees interface.
460-
Instead of returning the result of `f(n)` directly the result will be a tree of [`MapNode`](@ref) objects isomorphic
461-
to the original tree but with values equal to the corresponding `f(n)`.
457+
Apply the function `f` to every node in the tree with root `node`, where `f` is a function that returns `(v, ch)` where
458+
`v` is a new value for the node (i.e. as returned by [`nodevalue`](@ref) and `ch` is the new children of the node.
459+
`f` will be called recursively so that all the children returned by `f` for a parent node will again be called for
460+
each child. This means that to maintain the structure of a tree but merely map to new values, one should define
461+
`f = node -> (g(node), children(node))` for some function `g` which returns a value for the node.
462+
463+
The nodes of the output tree will all be represented by [`MapNode`](@ref) objects wrapping the returned values.
464+
This is necessary in order to guarantee that the output types can describe any tree topology.
462465
463466
Note that in most common cases tree nodes are of a type which depends on their connectedness and the function
464467
`f` should take this into account. For example the tree `[1, [2, 3]]` contains integer leaves but two
465468
`Vector` nodes. Therefore, the `f` in `treemap(f, [1, [2,3]])` must be a function that is valid for either
466469
`Int` or `Vector`. Alternatively, to only operate on leaves do `map(𝒻, Leaves(itr))`.
467470
468-
## Example
471+
It's very easy to write an `f` that makes `treemap` stack-overflow. To avoid this, ensure that `f` eventually
472+
termiantes, i.e. that sometimes it returns empty `children`. For example, if `f(n) = (nothing, [0; children(n)])` will
473+
stack-overflow because every node will have at least 1 child.
474+
475+
## Examples
469476
```julia
470477
julia> t = [1, [2, 3]];
471478
472-
julia> f(x) = x isa AbstractArray ? nothing : x + 1;
479+
julia> f(n) = n isa AbstractArray ? (nothing, children(n)) : (n+1, children(n))
473480
474481
julia> treemap(f, t)
475-
MapNode{Nothing, MapNode}(nothing)
476-
├─ MapNode{Int64, MapNode{Union{}}}(2)
477-
└─ MapNode{Nothing, MapNode{Int64, MapNode{Union{}}}}(nothing)
478-
├─ MapNode{Int64, MapNode{Union{}}}(3)
479-
└─ MapNode{Int64, MapNode{Union{}}}(4)
482+
nothing
483+
├─ 2
484+
└─ nothing
485+
├─ 3
486+
└─ 4
487+
488+
julia> g(n) = isempty(children(n)) ? (nodevalue(n), []) : (nodevalue(n), [0; children(n)])
489+
g (generic function with 1 method)
490+
491+
julia> treemap(g, t)
492+
Any[1, [2, 3]]
493+
├─ 0
494+
├─ 1
495+
└─ [2, 3]
496+
├─ 0
497+
├─ 2
498+
└─ 3
480499
```
481500
"""
482501
treemap(f, node) = MapNode(f, node)

test/trees.jl

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,15 @@ include(joinpath(@__DIR__,"examples","onetree.jl"))
8585
@test nodevalue.(collect(PostOrderDFS(n))) == [0,4,3,2]
8686
end
8787

88-
#=
89-
@testset "treemap!" begin
90-
# Test modification while iterating over PreOrderDFS
88+
@testset "treemap" begin
9189
a = [1,[2,[3]]]
92-
b = treemap!(PreOrderDFS(a)) do node
93-
!isa(node, Vector) && return node
94-
ret = pushfirst!(copy(node),0)
95-
# And just for good measure stomp over the old node to make sure nothing
96-
# is cached.
97-
empty!(node)
98-
ret
99-
end
100-
@test b == Any[0,1,Any[0,2,[0,3]]]
90+
f = n -> n isa AbstractArray ? (nothing, children(n)) : (n+1, children(n))
91+
b = treemap(f, a)
92+
@test nodevalue.(PreOrderDFS(b)) == [nothing, 2, nothing, 3, nothing, 4]
93+
g = n -> isempty(children(n)) ? (nodevalue(n), ()) : (nothing, [0; children(n)])
94+
b = treemap(g, a)
95+
@test nodevalue.(PostOrderDFS(b)) == [0, 1, 0, 2, 0, 3, nothing, nothing, nothing]
10196
end
102-
=#
10397

10498
#=
10599
struct IntTree

0 commit comments

Comments
 (0)