A function like copyto! that acts recursively as deepcopy

Recently I stumbled upon a case where I for example have an array of arrays (a) and a second memory already allocated (b) – this might also be deeper nested than in the following example.

a = [[1],[2],[3]]
b = similar.(a)

Now if I want to copy the values and not the arrays, copyto! won’t work with

copyto!(b,a)
b[3][1] = 4

I would also change a. What’s the best way to copy the values (not references) of the inner arrays (recursively) as deepcopy would do? Note that b is allocated already, so deepcopy would allocate new memory here, which I would like to avoid.

Eg

map(copyto!, b, a)

but there are a lot of equivalent solutions (broadcasting, a loop, etc).

Thanks.
But this would just be one level of recursion, i.e. not work for arrays of arrays of arrays? (Similarly would copyto!.(a,b), right?
I would like a method that – as deep copy – does this on arbitrary levels. And sure, my MWE Is too small for that.

Something like

function recursive_copyto!(a::AbstractArray{T}, b::AbstractArray{T}) where {T}
    if T <: AbstractArray
        foreach(recursive_copyto!, a, b)
    else
        copyto!(a, b)
    end
    a
end

?

1 Like

That looks nice, maybe I would do that with dispatch?

function recursive_copyto!(a::AbstractArray{T}, b::AbstractArray{T}) where {T<:AbstractArray}
    foreach(recursive_copyto!, a, b)
    return a
end
function recursive_copyto!(a::AbstractArray{T}, b::AbstractArray{T}) where {T}
    copyto!(a, b)
end

Sure, if you prefer doing the job of the compiler.

1 Like

Question. Given that arrays of arrays are in reality arrays of references to other arrays, and that they can change in size, etc.

When you do

a = [[1],[2],[[3,4,5]]]
b = similar.(a)

Are you allocating only enough space to get three pointers to other arrays, or are you really allocating the space for the three pointers and space for one Int associated with those pointers and then three ints in the inner array? I figure if your inside array has a more complicated layout I may become more difficult to really allocate everything at once?

The example above yields:

julia> a = [[1],[2],[[3,4,5]]]
3-element Vector{Vector{T} where T}:
 [1]
 [2]
 [[3, 4, 5]]

julia> b = similar.(a)
3-element Vector{Vector{T} where T}:
 [4831755376]
 [4660659088]
 [#undef]

What I wonder if is you really save something preallocating this instead of just doing a deepcopy

For types I prefer to not write it wit an if, since I find the code cleaner. Is your code faster?

Oh, the point is, that in the original code my b is already initialised fully, since it comes from somewhere else, so there is no deep copy I would like to do, I really just want to copy the values. So there is no similar.(...) in my code. Initialilzation happens far before and I am sure both a and b are initialised and of same size, it actually is a point on the nested array PowerManifold (and maybe even a point on a power manifold of a power manifold, so even 3 levels of arrays)

Sorry if my MWE caused confusion there.

Oh, thanks for explaining and sorry for adding noise.

No problem – actually we also solve this recursive similar problem you stumbled upon, which is (similar as asked here) just recursively applying similar, we call it allocate in our package, see https://juliamanifolds.github.io/Manifolds.jl/latest/interface.html#Allocation.

No, there should be no speed difference. What you find cleaner is of course a matter of taste, just do it the way you prefer.

1 Like

Thanks for the clarification. Sure that is just a matter of taste. That’s why I also marked your answer as the solution. Thanks for the help