About mapping functions and a deepmap!() function

I think that may already exist but I can’t remember where. But if it doesn’t, good idea!

[Edit: it does in fact mutate,I’m just blind]
[[quote=“rapasite, post:1, topic:51702”]
deepmap!
[/quote]

Since you collect over all containers, non of this is mutating, so it should be deepmap without the exclamation mark :slight_smile:]

While this does work, I think a more generic approach would be:

deepmap(f, arg) = f(arg)
deepmap(f, a::AbstractArray) = collect(a[i] = deepmap(f,a[i]) for i in eachindex(a))

# for comparison: your your manually specialized approach
your_deepmap!(f,n::Number) = f(n)
your_deepmap!(f,s::AbstractString) = f(s)
your_deepmap!(f,c::AbstractChar) = f(c)
your_deepmap!(f,a::AbstractArray) = collect(a[i] = your_deepmap!(f,a[i]) for i in eachindex(a))

which is equivalent in performance, since Julia specializes the generic deepmap(f, arg) = f(arg) for each argument type. This also helps with the definition of custom behaviour, since any narrowly typed method overwrites the generic version. Performance:

julia> @benchmark deepmap(x->(x*2), A) setup = A = [ [rand(Int) for _ in 1:rand(10:20)] for _ in 1:rand(10:20)]
BenchmarkTools.Trial: 
  memory estimate:  1.95 KiB
  allocs estimate:  12
  --------------
  minimum time:     413.573 ns (0.00% GC)
  median time:      734.671 ns (0.00% GC)
  mean time:        837.348 ns (9.61% GC)
  maximum time:     9.399 μs (84.81% GC)
  --------------
  samples:          10000
  evals/sample:     199

julia> @benchmark your_deepmap!(x->(x*2), A) setup = A = [ [rand(Int) for _ in 1:rand(10:20)] for _ in 1:rand(10:20)]
BenchmarkTools.Trial: 
  memory estimate:  1.86 KiB
  allocs estimate:  12
  --------------
  minimum time:     408.035 ns (0.00% GC)
  median time:      736.688 ns (0.00% GC)
  mean time:        833.854 ns (9.28% GC)
  maximum time:     8.549 μs (82.08% GC)
  --------------
  samples:          10000
  evals/sample:     199

# and for containers with mixed types (reduced input array)

julia> @benchmark deepmap(x->(x*2), A) setup = A = [ [rand(Bool) ? rand(Int) : rand(Float64) for _ in 1:rand(5:6)] for _ in 1:rand(5:6)]
BenchmarkTools.Trial: 
  memory estimate:  2.08 KiB
  allocs estimate:  59
  --------------
  minimum time:     3.300 μs (0.00% GC)
  median time:      4.537 μs (0.00% GC)
  mean time:        5.049 μs (3.00% GC)
  maximum time:     300.137 μs (95.68% GC)
  --------------
  samples:          10000
  evals/sample:     8

julia> @benchmark your_deepmap!(x->(x*2), A) setup = A = [ [rand(Bool) ? rand(Int) : rand(Float64) for _ in 1:rand(5:6)] for _ in 1:rand(5:6)]
BenchmarkTools.Trial: 
  memory estimate:  2.08 KiB
  allocs estimate:  59
  --------------
  minimum time:     3.250 μs (0.00% GC)
  median time:      4.475 μs (0.00% GC)
  mean time:        4.939 μs (3.06% GC)
  maximum time:     282.850 μs (96.25% GC)
  --------------
  samples:          10000
  evals/sample:     8

Yes, I’d add a reverse counting integer argument which stops recursion once it reaches zero:

julia> deepmap(f, arg, ::Int) = f(arg)
deepmap (generic function with 1 method)

julia> deepmap(f, a::AbstractArray, depth::Int) = depth===0 ? a : collect(a[i] = deepmap(f, a[i], depth-1) for i in eachindex(a)) #optionally add a default value
deepmap (generic function with 2 methods)

# for testing:

julia> gentower(depth, counter = 1) = return (depth==counter ? Any[counter,]  : Any[counter, gentower(depth, counter+1) ,counter]) # any to be able to change types, not necessary
gentower (generic function with 2 methods)

julia> print(gentower(8))
Any[1, Any[2, Any[3, Any[4, Any[5, Any[6, Any[7, Any[8], 7], 6], 5], 4], 3], 2], 1]

which does:

julia> deepmap(x->(string(x)), gentower(5), 5)
3-element Array{Any,1}:
 "1"
 Any["2", Any["3", Any["4", ["5"], "4"], "3"], "2"]
 "1"

julia> deepmap(x->(string(x)), gentower(5), 4)
3-element Array{Any,1}:
 "1"
 Any["2", Any["3", Any["4", Any[5], "4"], "3"], "2"]
 "1"

julia> deepmap(x->(string(x)), gentower(5), 3)
3-element Array{Any,1}:
 "1"
 Any["2", Any["3", Any[4, Any[5], 4], "3"], "2"]
 "1"

julia> deepmap(x->(string(x)), gentower(5), 2)
3-element Array{Any,1}:
 "1"
 Any["2", Any[3, Any[4, Any[5], 4], 3], "2"]
 "1"

julia> deepmap(x->(string(x)), gentower(5), 1)
3-element Array{Any,1}:
 "1"
 Any[2, Any[3, Any[4, Any[5], 4], 3], 2]
 "1"

julia> deepmap(x->(string(x)), gentower(5), 0)
3-element Array{Any,1}:
 1
  Any[2, Any[3, Any[4, Any[5], 4], 3], 2]
 1

But it’s efficiency is questionable, I think there are more clever ways to do that:

julia> @benchmark deepmap(x->(string(x)), A, 5) setup = A = gentower(10)
BenchmarkTools.Trial: 
  memory estimate:  1.90 KiB
  allocs estimate:  39
  --------------
  minimum time:     2.744 μs (0.00% GC)
  median time:      2.900 μs (0.00% GC)
  mean time:        3.349 μs (3.56% GC)
  maximum time:     317.833 μs (97.66% GC)
  --------------
  samples:          10000
  evals/sample:     9

[rest follows in a separate post]