Skip to content

Commit

Permalink
Unroll the recursive function into iteration
Browse files Browse the repository at this point in the history
This avoids a StackOverflow error, and also turns out to be faster
still, and lets us maintain the ability for map to return a list of a
different eltype than the original, added in #27.

Before:
```julia
julia> @Btime DataStructures.map(x->x*2, $(list((1:1000)...)));
  29.705 μs (1745 allocations: 42.89 KiB)
```
After:
```julia
julia> @Btime DataStructures.map(x->x*2, $(list((1:1000)...)));
  13.139 μs (1011 allocations: 47.65 KiB)
```

I'm not sure how there are fewer allocations.. But I can see how it'd be
faster, with no recursive function calls.
  • Loading branch information
NHDaly committed Oct 16, 2021
1 parent fa11333 commit 357f597
Showing 1 changed file with 14 additions and 2 deletions.
16 changes: 14 additions & 2 deletions src/list.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,20 @@ end
Base.map(f::Base.Callable, l::Nil) = l

function Base.map(f::Base.Callable, l::Cons{T}) where T
rest = Base.map(f, l.tail)
return cons(f(l.head), rest)
# Tail recursion manually unrolled into iterative loop with local stack to avoid
# StackOverflow. The recursive logic is:
# map(f::Base.Callable, l::Cons) = cons(f(head(l)), map(f, tail(l)))
# Recursion to iteration instructions: https://stackoverflow.com/a/159777/751061
stack = Vector{T}()
while !(tail(l) isa Nil)
push!(stack, head(l))
l::Cons{T} = tail(l)::Cons{T}
end
l2 = list(f(head(l))) # Note this might have a different eltype than T
for i in reverse(1:length(stack))
l2 = cons(f(stack[i]), l2)
end
return l2
end

function Base.filter(f::Function, l::LinkedList{T}) where T
Expand Down

0 comments on commit 357f597

Please sign in to comment.