I'm confused about the different types of arrays of arrays. Consider these two examples
a = Array{Float64}[]
push!(a,[1, 2])
push!(a,[3, 4])
push!(a,[1 2; 3 4])
b = Array[[1.0, 2.0], [3.0,4.0], [1.0 2.0; 3.0 4.0]]
I'm not sure how a
and b
differ. Suppose I intend to run a for loop over each of the elements in a
or b
, and multiple each element by 2. That is,
for i in 1:3 a[i] = a[i]*2 end
for i in 1:3 b[i] = b[i]*2 end
I time the run time of both lines respectively, but they are equally fast. Are a
and b
the same? If so, why does a
even exist? It looks fairly complicated, because typeof(a)
yields "Array{Array{Float64,N} where N,1}". What does where
do here?
Both a
and b
are vectors but they allow different type of elements. You can check this by writing:
julia> typeof(a)
Array{Array{Float64,N} where N,1}
julia> typeof(b)
Array{Array,1}
Now Array
isa parametric type taking two parameters. The first parameter is type of elements that it allows. The second parameter is the dimension. You can see that in both cases the dimension is 1
which means both a
and b
are vectors. You can also check it using the ndims
function:
julia> ndims(a)
1
julia> ndims(b)
1
The first parameter is allowed element type. In the case of a
it is Array{Float64,N} where N
while in the case of b
just Array
is printed. Before I explain how to read them note that the first parameter can be extracted using the eltype
function:
julia> eltype(a)
Array{Float64,N} where N
julia> eltype(b)
Array
You can see that both a
and b
allow Array
to be stored. First let me explain how to read Array{Float64, N} where N
. It means that a
allows storing of arrays of Float64
of any dimension. Actually you could have written in a shorter way like this Array{Float64}
as you can check that:
julia> (Array{Float64,N} where N) === Array{Float64}
true
The reason is that if you do not put a restriction on a tail parameter it can be dropped in syntax. The where N
part is a restriction on the parameter. In this case there is no restriction on the second parameter.
Now we can turn to b
. You see its eltype
is just Array
, so both parameters are dropped, thus there are no restrictions on them as explained above. So Array
is the same as Array{T, N} where {T,N}
as you can see here:
julia> (Array{T, N} where {T,N}) === Array
true
So the difference is that a
can store arrays of any dimension but they must have Float64
element type, while b
can store arrays of any dimension and any element type. The distinction, in this case, has no performance impact as you have noted, but will have an impact on what can be stored in a
and b
. Here are some examples.
In this case they work the same, as you try to store an Int
in them, but they allow only arrays:
julia> a[1] = 1
ERROR: MethodError: Cannot `convert` an object of type
julia> b[1] = 1
ERROR: MethodError: Cannot `convert` an object of type
but here they differ:
julia> a[1] = ["a"]
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Float64
julia> b[1] = ["a"]
1-element Array{String,1}:
"a"
julia> a
3-element Array{Array{Float64,N} where N,1}:
[1.0, 2.0]
[3.0, 4.0]
[1.0 2.0; 3.0 4.0]
julia> b
3-element Array{Array,1}:
["a"]
[3.0, 4.0]
[1.0 2.0; 3.0 4.0]
As you can see you can store an array of String
in b
, but this is not allowed to be stored in a
.
Two additional comments (both topics are a bit complex so I leave out the details, but just give you hints what is going on):
julia> [[1.0, 2.0], [3.0,4.0], [1.0 2.0; 3.0 4.0]]
3-element Array{Array{Float64,N} where N,1}:
[1.0, 2.0]
[3.0, 4.0]
[1.0 2.0; 3.0 4.0]
The performance impact of the choice of element type of an array depends on the fact:
isabstracttype
; this impact type inference made by the compiler),isbitstype
; this impacts the storage layout),Union
(small unions can are handled more efficiently).In your case both element types are abstract and non-bits so the performance will be the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With