How to initialize an array of structs

I want to create an array of structs like shown below

mutable struct mystruct
    a::Float32
    b::Float32
    c::UInt32
end

mystruct() = mystruct(0,0,0)

size = 3

a = Array{mystruct, 3}(undef, size, size, size)


for i in eachindex(a)
    a[i] = mystruct()
end

Is there anyway to initialize the structs in the array without looping over the array? For instance something like:

a = Array{mystruct, 3}({0,0,0}, size, size, size)
# or
a = Array{mystruct, 3}(mystruct(), size, size, size)
1 Like

Technically there’s going to be an underlying loop somewhere, but the fill(x, dims...) and fill!(A, x) methods do this. Just bear in mind that they assign the same single instance x to all the elements, so if the instance is mutable and you mutate it, all the elements see the change.

If you want each element to have different instances, an explicit loop is the general way, but judging from your example, you could reasonably define a Base.zero(::Type{mystruct}) = mystruct(0,0,0) method, then call zeros(mystruct, dims...). Note that zeros takes the type instead of an instance as an argument, so it can do zero(mystruct) in the underlying loop to make a new instance per element.

Or a comprehension:

[ mystruct() for _ in 1:n1, j in _ 1:n2, k in _ 1:n3 ]
6 Likes

Note that a comprehension evaluates mystruct(), or whatever code you write there, per iteration just like a for-loop body, so each element gets a different instance. Although it may look more similar to the fill(mystruct(), dims...) call at a glance, the difference there is mystruct() get evaluated only once, then the returned instance is passed as an argument into a fill call.

2 Likes

Sure, if the structure is mutable most likely different instances is what the OP wants.

Thanks for your input. I understand there is going to be a loop somewhere I just wanted a convenient way to make initialize it without having to explicitly write the loop.

Different instances for each element is what I want so either defining a new zero function or the comprehension is the answer.

You can simply use dot assigment with a scalar on the right side:

a .= Ref(mystruct(0,0,0))

That will still assign the same instance to every index.

2 Likes

I had a similar problem but my struct starts with its fields already initialized due to my use case. So I created an array a that accepts only instances of mystruct, and I know beforehand the length a. Instead of iterating over each element you can use map to each index of a as:

a = Vector{mystruct}(under, 5)
map(x -> a[x] = mystruct(), eachindex(a))

The result is the same as what you initially proposed but I found that fill and fill! populate the array with the same instance of mystruct. And I could not use zeros because the fields are initialized beforehand.

Using dot assignment as mentioned by @rafael.guerra fills a with the same instance of mystruct. If you modify a field, for example, from a[1], then the rest of the entries in a for that field will have the same value.

Also, as pointed out by @lmiq, the list comprehension also gets the job done as you want, but I like using map better.

Maybe something like this :

mutable struct mystruct
    a::Float32
    b::Float32
    c::UInt32
end

mystruct() = mystruct(0,0,0)

Base.:zero(T ::Type{mystruct}) = mystruct()
size = 3
a = zeros(mystruct, (size,size,size))
println(a)
println(a[1,1,1])