Automate allocation

Is it possible to automate this:

#I currently have:
y_TX, m_TX, ŷ_TX, ATT_TX, ATT_co, se_i_TX, se_t_TX = [], [], [], [], [], [], [] 
# I want something like:
y_TX, m_TX, ŷ_TX, ATT_TX, ATT_co, se_i_TX, se_t_TX = []
1 Like

You can do:

y_TX, m_TX, ŷ_TX, ATT_TX, ATT_co, se_i_TX, se_t_TX = ntuple(_ -> [], 7)
8 Likes

You can do something like

a, b, c = ([] for _ in 1:3)

Be careful, because that way the arrays are of type Any, thus inefficient.

3 Likes

Thanks but I keep adding new variables as I write, is it possible to do something like

a, b, c = ([] for _ in 1:end)
1 Like

You could just pick a really big upper bound, only the elements of the iterator you actually use will be created:

a, b, c       = ([] for _ in 1:1000)
a, b, c, d    = ([] for _ in 1:1000)
a, b, c, d, e = ([] for _ in 1:1000)
# etc...
6 Likes

Could use Iterators.repeated here:

julia> using .Iterators: repeated

julia> a, b, c = repeated([]);

julia> a
Any[]

julia> b
Any[]

julia> c
Any[]
2 Likes

That just repeats the same object though, using the comprehension makes independent []'s.

7 Likes
julia> using .Iterators: repeated

julia> a, b, c = repeated([]);

julia> push!(a, 2)
1-element Array{Any,1}:
 2

julia> b
1-element Array{Any,1}:
 2

3 Likes

If you do not mind a new struct type:

struct OnRepeat{F}
    f :: F
end

function Base.iterate(o :: OnRepeat{F}, s :: Nothing = nothing) where {F}
    return o.f(), nothing
end

a, b, c = OnRepeat(() -> [])

And now you can use OnRepeat with any function taking no arguments and returning the new object you want.

7 Likes

good catch!

1 Like

I’ve sometimes defined a macro to help with this exact scenario,

macro repeated(x)
    :(($(esc(x))) for _ in Iterators.repeated(nothing))
end

then you can do

a, b, c = @repeated([])

I think I even proposed adding this to Base.Iterators but there were some valid concerns which I forget the details of now though.

5 Likes

could also just do

copy_repeated(x) = (copy(x) for x in Iterators.repeated(x))

then

julia> a, b, c = copy_repeated([])
Base.Generator{Base.Iterators.Repeated{Vector{Any}}, typeof(copy)}(copy, Base.Iterators.Repeated{Vector{Any}}(Any[]))

julia> push!(a, 1)
1-element Vector{Any}:
 1

julia> b
Any[]

(or Iterators.map(copy, repeated(x)) if you’re on 1.6+

6 Likes

There are some issues w/ some of the methods proposed above.

Method 1:

julia> zs = zeros(3)
3-element Vector{Float64}:
 0.0
 0.0
 0.0

julia> a, b, c = (zs for _ in 1:100)
Base.Generator{UnitRange{Int64}, var"#33#34"}(var"#33#34"(), 1:100)

julia> a, b, c, zs
([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> b = ones(3)
3-element Vector{Float64}:
 1.0
 1.0
 1.0

julia> a, b, c, zs # only b is changed
([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> a[1:2] = ones(2) 
2-element Vector{Float64}:
 1.0
 1.0

julia> a, b, c, zs # a, c, zs are changed, but not b. Why?
([1.0, 1.0, 0.0], [1.0, 1.0, 1.0], [1.0, 1.0, 0.0], [1.0, 1.0, 0.0])

When I change b to a new vector, only b is changed, while a, c, zs are unchanged.
When I change some elements of a to a new vector, then a, c, zs are all changed in the same way, while b is unchanged.

Method 2:

julia> copy_repeated(x) = (copy(x) for x in Iterators.repeated(x))
copy_repeated (generic function with 1 method)

julia> zs = zeros(3)
3-element Vector{Float64}:
 0.0
 0.0
 0.0

julia> a, b, c = copy_repeated(zs)
Base.Generator{Base.Iterators.Repeated{Vector{Float64}}, typeof(copy)}(copy, Base.Iterators.Repeated{Vector{Float64}}([0.0, 0.0, 0.0]))

julia> a, b, c, zs
([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> b = ones(3)
3-element Vector{Float64}:
 1.0
 1.0
 1.0

julia> a, b, c, zs # only b is changed
([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> a[1:2] = ones(2) 
2-element Vector{Float64}:
 1.0
 1.0

julia> a, b, c, zs # only a is changed
([1.0, 1.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

@Mason’s method gives me what I expected.

Q: is the behavior of the 1st method a, b, c = (zs for _ in 1:100) as intended?

Here you assigned b to be a new vector of ones. If you wanted to mutate the vector that was there, you must use b .= 1, or b[1] = 1, etc.:

julia> zs = zeros(3);

julia> a, b, c = (zs for _ in 1:3)
Base.Generator{UnitRange{Int64}, var"#1#2"}(var"#1#2"(), 1:3)

julia> a
3-element Vector{Float64}:
 0.0
 0.0
 0.0

julia> a[1] = 1
1

julia> a,b,c
([1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0])


Ah, I see you already noted that. Yes, that is the expected behavior. If you don’t want the vectors to be the same, you need to copy them:

julia> a, b, c = (copy(zs) for _ in 1:3)
Base.Generator{UnitRange{Int64}, var"#3#4"}(var"#3#4"(), 1:3)

julia> a
3-element Vector{Float64}:
 1.0
 0.0
 0.0

julia> a[1] = 2
2

julia> a, b, c
([2.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0])


3 Likes

Yes.
We talked about assignment vs copy here.

I brought this up in case future users come to this thread, so the diff betw the following is clear:

a, b, c = (zs for _ in 1:3)
a, b, c = (copy(zs) for _ in 1:3)

a, b, c = (copy(zs) for i in Iterators.repeated(zs))
3 Likes