4
votes

I am trying to build a two element array in Julia, where each sub-array has a different type (one is a vector of Int64s, the other is an array of Float32s).

The code belows automatically converts the element that I want to be an Int64 into a Float32, which is what I don't want:

my_multitype_array = [ collect(1:5), rand(Float32,3) ]

The resulting array automatically converts the Int64s in the first array (defined via collect(1:5)) into a Float32, and the resulting my_multitype_array has type 2-element Array{Array{Float32,1}}. How do I force it to make the first sub-array remain Int64s? Do I need to perhaps pre-define my_multitype_array to be an empty array with two elements of the desired types, before filling it out with values?

And finally, once I do have the desired array with different types, how would I refer to it, when pre-stating its type in a function? See below for what I mean:

function foo_function(first_scalar_arg::Float32, multiple_array_arg::Array{Array{Float32,1}})
       # do stuff
       return
end

Instead of ::Array{Array{Float32,1}}, would I write ::Array{Array{Any,1}} or something?

3
As in the answers you get you can use a Union type. But you could also think if the outer structure would be a tuple rather than an array. That would be likely even more efficient, as it would know the two inner types at compile time.Antonello

3 Answers

4
votes

I think that the following code matches better what was asked in the question:

julia> a = Union{Array{Int},Array{Float64}}[[1,2,3],rand(2,2)]
2-element Array{Union{Array{Float64,N} where N, Array{Int64,N} where N},1}:
 [1, 2, 3]
 [0.834902264215698 0.42258382777543124; 0.5856562680004389 0.6654033155981287]

This creates an actual data structure which knows that it contains either Float64 or Int arrays.

Some usage

julia> a[1]
3-element Array{Int64,1}:
 1
 2
 3

julia> a[2]
2×2 Array{Float64,2}:
 0.834902  0.422584
 0.585656  0.665403

And manipulating the structure:

julia> push!(a, [1, 1]); #works

julia> push!(a, [true, false]);
ERROR: MethodError: no method matching Union{Array{Float64,N} where N, Array{Int64,N} where N}(::Array{Bool,1})
2
votes

How to instantiate a vector of different types:

If you type the vector in a terminal, it will be promoted to the largest common type:

julia> [[1], [1.0]]
2-element Array{Array{Float64,1},1}:
 [1.0]
 [1.0]

The reason for that is that you don't specify the type of the outer vector, so Julia will try to infer the type based on the contents. More specific types are always more efficient, so if the vector types can be converted to a single type that can represent all the inner vectors, this will be done (through the promote mechanism). To avoid it, you need to manually specify the outer vector type e.g.:

julia> Any[[1], [1.0]]
2-element Array{Any,1}:
 [1]
 [1.0]

How to refer to vectors of differently-typed vectors

When you think about it, "vectors of differently-typed vectors" is not a single type, but an infinite set of types. These kind of types are called "unionall types" in Julia, and are represented by the where keyword. In this case, you want Vector{T} where T <: Vector.

But wait! Then how come:

julia> Any[[1], [1.0]] isa Vector{T} where T <: Vector
false

Well, a vector that can contain any element is not really a vector of vectors. So here you have two options:

  • Either relax your function signature by just removing the type annotations or relatixing them significantly (this is preferred because the value you pass in may actually be a vector of vectors even if its type is e.g. Vector{Any}):
function foo_function(first_scalar_arg, multiple_array_arg::AbstractArray)
       # do stuff
       return
end
  • Or else be vigilant that you make sure to construct a "vector of vectors" initially:
julia> Vector[[1], [1.0]]
2-element Array{Array{T,1} where T,1}:
 [1]
 [1.0]

julia> Vector[[1], [1.0]] isa Vector{T} where T <: Vector
true
2
votes

To expand a little on @Przemyslaw Szufel's answer...

Creating vectors with elements of mixed types is tricky, as you've seen, since the literal array constructor attempts to promote the elements to a common type. There is a special syntax to get around that, which is described in the manual here.

In your case, you can construct your vector of vectors as follows:

julia> Union{Vector{Int64}, Vector{Float32}}[[1, 2], [1.0f0, 2.0f0]]
2-element Array{Union{Array{Float32,1}, Array{Int64,1}},1}:
 [1, 2]
 Float32[1.0, 2.0]

The prefix to the literal array constructor specifies the element type of the array. So in this case, the element type of the vector is constrained to be

Union{Vector{Int64}, Vector{Float32}}

In other words, the elements of the outer vector must be either vectors of Int64 or vectors of Float32.