How are vectors of vectors type coerced?

I was having a bug in my code, which basically boils down to this.

inds = [1,2,3] # Vector{Int}
vals = [1.5, 2.5, 3.5] # Vector{Float64}
ivvec = [inds, vals] # Vector{Vector{Float64}}
ivvec[2][ivvec[1]] #ERROR: ArgumentError: invalid index: 1.0 of type Float64

The issue is this:

inds # Vector{Int}
ivvec[1] # Vector{Float64}

Even though inds is a Vector{Int}, ivvec[1] is a Vector{Float64}. Once found, this bug is easy to fix. ivvec = Any[inds, vals] or ivvec = (inds, vals) both do the trick.

Another weird thing is that inds is copied, while vals is just referenced:

inds[1] = 5 # 5
ivvec[1][1] # 1.0
vals[1] = 5.5 # 5.5
ivvec[2][1] # 5.5

Is this the intended behavior? What exactly is happening here?

I don’t have the time to dive into the specific details here, but a general advise: vectors of vectors are rarely a good design pattern in Julia. You have Matrix, N-dimensional Array, StructArrays.jl, Tables.jl and many other data structures that are suitable for efficient lookups with nested buffers.

One solution is to define a Union:

inds = [1,2,3] # Vector{Int}

vals = [1.5, 2.5, 3.5] # Vector{Float64}

ivvec = Union{Vector{Int},Vector{Float64}}[inds, vals]

Otherwise, ivvec will be promoted to Vector{Vector{Float64}}.

1 Like

Typically, containers are referenced, but promotion breaks this behavior presumably because the values in the containers are not longer the same. The Union type should work as expected.

If your provide more details about your use case, someone might be able to extend juliohm’s advice.

I am really surprised about this. I would have thought for sure it would automatically be a Vector{Any}. Breaking of the reference seems particularly odd.

What’s the rationale for doing promotion here? Or rather, what’s the general rule for when promotion happens?

1 Like

From Single- and multi-dimensional Arrays · The Julia Language

If they all have a common promotion type then they get converted to that type using convert and that type is the array’s eltype

A somewhat abstract advice is to use Vector for elements which are used for “the same thing”. In your particular example, it would be more tidy to use a Tuple:

ivvec = (inds, vals)
2 Likes

Thanks for the info! I see a lot of people trying to “solve my problem,” when there’s a solution right there in the post. I was more just curious about why this happens and whether it’s a bug or a feature.

I guess each step makes sense as a rule,

  • automatic promotion in a vector
  • promoting a Vector{Int} into a Vector{Float64}
  • copying promoted vectors but referencing non-promoted vectors
  • not being able to use a Float64 as an index

even if taken together they lead to some unintuitive behavior.

I guess if there are Julian lessons here, they are

  1. Avoid vectors of vectors
  2. Don’t make vectors of different things