Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Julia: array of arrays with different types

Julia newbe here, transitioning from python.

So, I want to build what in Python I would call list, made of lists made of lists. In my case, it's a 1000 long list whose element is a list of 3 lists.

Until now, I have done it this way:

BIG_LIST = collect(Array{Int64,1}[[],[],[]] for i in 1:1000)

This served my purpose when all three most inner lists where made of integers. Now I need 2 of them to be of integers, while the third of Float. Is this possible? How do I do it?

If you could also explain better how to properly initialize these objects that would be great. I am aware that collect is not the best choice here.

Note that the length of the 3 inner lists is the same among the 3, but can vary during the process.

like image 353
tidus95 Avatar asked Feb 26 '20 16:02

tidus95


2 Answers

First, if you know that intermediate lists always have 3 elements, you'll probably be better off using Tuple types for those. And tuples can specify independently the types of their elements. So something like this might suit your purposes:

julia> l = [(Int64[], Int64[], Float64[]) for _ in 1:10]
10-element Array{Tuple{Array{Int64,1},Array{Int64,1},Array{Float64,1}},1}:
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])
 ([], [], [])

julia> push!(l[1][3], 5)
1-element Array{Float64,1}:
 5.0

julia> l
10-element Array{Tuple{Array{Int64,1},Array{Int64,1},Array{Float64,1}},1}:
 ([], [], [5.0])
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   
 ([], [], [])   

A few details to note here, that might be of interest to you:

  • Empty but typed lists can be constructed using T[], where T is the element type.

  • collect(f(i) for i in 1:n) is essentially equivalent to a simple comprehension (like you're used to in python): [f(i) for i in 1:n]. Note that since variable i plays no role here, you can replace it with a _ placeholder so that it more immediately appears to the reader that you're essentially creating a collection of similar objects (but not identical, in the sense that they don't share the same underlying memory; modifying one won't affect the others).

  • I don't know of any better way to initialize such a collection and I wouldn't think that using collect(or a comprehension) is a bad idea here. For collections of identical objects, fill provides a useful shortcut, but it wouldn't apply here because all sub-lists would be linked.




Now, if all inner sublists have the same length, you might want to switch to a slightly different data structure: a vector of vectors of tuples:

julia> l2 = [Tuple{Int64,Int64,Float64}[] for _ in 1:10]
10-element Array{Array{Tuple{Int64,Int64,Float64},1},1}:
 []
 []
 []
 []
 []
 []
 []
 []
 []
 []

julia> push!(l2[2], (1,2,pi))
1-element Array{Tuple{Int64,Int64,Float64},1}:
 (1, 2, 3.141592653589793)

julia> l2
10-element Array{Array{Tuple{Int64,Int64,Float64},1},1}:
 []                         
 [(1, 2, 3.141592653589793)]
 []                         
 []                         
 []                         
 []                         
 []                         
 []                         
 []                         
 []                         
like image 182
François Févotte Avatar answered Sep 19 '22 16:09

François Févotte


Francois has given you a great answer. I just wanted to raise one other possibility. It sounds like your data has a fairly complicated, but specific, structure. For example, the fact that your outer list has 1000 elements, and your inner list always has 3 lists...

Sometimes in these situations it can be more intuitive to just build your own type(s), and write a couple of accessor functions. That way you don't end up doing things like mylist[3][2][6] and forgetting which index refers to which dimension of your data. For example:

struct MyInnerType
    field1::Vector{Int}
    field2::Vector{Int}
    field3::Vector{Float64}
end
struct MyOuterType
    x::Vector{MyInnerType}
    function MyOuterType(x::Vector{MyInnerType})
        length(x) != 1000 && error("This vector should always have length of 1000")
        new(x)
    end
end

I'm guessing here, but perhaps accessor functions like this would be useful for, e.g. field3:

get_field3(y::MyInnerType, i::Int)::Float64 = y.field3[i]
get_field3(z::MyOuterType, iouter::Int, iinner::Int)::Float64 = get_field3(z.x[iouter], iinner)

Remember that there is no performance penalty to using your own types in Julia.

One other thing, I've included all type information in my functions above for clarity, but this is not actually necessary for getting maximum performance either.

like image 43
Colin T Bowers Avatar answered Sep 20 '22 16:09

Colin T Bowers