Why are irregularly shaped arrays of arrays of type Any in Julia?

Hi, this is a bit of an edge case coming from trying something out with yesterday’s adventOfCode challenge (day 18).

Why are irregularly shaped arrays of arrays of type Any?

For example:

julia> [[2, 9], 3]
2-element Vector{Any}:
  [2, 9]
 3

But as long as I keep the nested arrays are of the same size, the type remains Int:

julia> [[2, 9], [3, 4]]
2-element Vector{Vector{Int64}}:
 [2, 9]
 [3, 4]

Not exactly, because e.g.:

julia> [[2, 9], [3]]
2-element Vector{Vector{Int64}}:
 [2, 9]
 [3]

Your first example is not an array of arrays, it’s an array with one element Array and another element Int.

14 Likes

Ah, and welcome here!

2 Likes

Probably the above is what you want, but if not you could do:

julia> Union{Vector{Int},Int}[ [2,9], 3 ]
2-element Vector{Union{Int64, Vector{Int64}}}:
  [2, 9]
 3

julia>

The advent of code challenge also has more complicated sequences to be read from a text file. I was curious if it was possible to read it directly into well structured nested arrays. Ideally, I would have liked Julia to figure out Union{Vector{Int},Int} from @lmiq automatically, such that it would work for any complicated structure like bellow (full input here):

[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]

I omitted to mention that I read the file like this:

d = open("18.txt") do f readlines(f) end
d = eval.(Meta.parse.(d))

I am not sure this is a relevant question, because these adventofcode challenges can be a bit far from real use cases anyways and also because this challenge can also be solved by reading the structures as strings and count brackets, as opposed to running eval.

It doesn’t really matter much since as soon as you have to represent everything as pointers to boxed objects, you’re already hosed in performance anyway. What the problem with the arrays being Any typed?

2 Likes

Wouldn’t union splitting help with subsequent code while indexing into the array, if the type is known to be a small Union instead of Any? Or is that not a factor?

You can do this to get a fully typed object:

julia> pairify(x::Int) = x
pairify (generic function with 1 method)

julia> pairify((a, b)::Vector) = pairify(a) => pairify(b)
pairify (generic function with 2 methods)

julia> number = pairify(eval(Meta.parse("[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]")))
((0 => (5 => 8)) => ((1 => 7) => (9 => 6))) => ((4 => (1 => 2)) => ((1 => 4) => 2))

julia> typeof(number)
Pair{Pair{Pair{Int64, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}}, Pair{Pair{Int64, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Int64}}}

Sadly you will find that when you try to solve the rest of the problem, compile time will absolutely explode due to all the type specializations you run into. The @nospecialize macro might help but I wasn’t able to get reasonable performance with this approach before I changed track.

Vector{Any} isn’t that bad an idea.

3 Likes

It might but only in the simple cases. These deeply nested examples are too complex for the compiler to make heads or tails of.