Array of tuples


#1

I have an array A of tuples Array{TupleFloat64,Float64},1}, but am not able to make two arrays out of this like:
I1 = A[:][1]
I2 = A[:][2
even though I can access the individual elements with A[i][j]


#2

Please give a minimal working example in the future (in this case, a small example case of your array A. Also please quote your code with backticks.

One solution is

I1 = first.(A)
I2 = last.(A)

Another is

I1 = [x[1] for x in A]

#3

Dear David,

Thank you for providing a solution. I am an experienced C and C++ programmer but a newcomer to Julia. Is there a deeper reason why A[:][1] does not work to make an array with the first element of the tupple?

Kind regards,

Jacques


#4

A[:] is the same as A for a vector, regardless what is inside, indexing out all elements.

julia> A=[(1, 2), (2, 3), (3, 4)]
3-element Array{Tuple{Int64,Int64},1}:
 (1, 2)
 (2, 3)
 (3, 4)

julia> A[:]
3-element Array{Tuple{Int64,Int64},1}:
 (1, 2)
 (2, 3)
 (3, 4)

so A[:][1] is the same as A[1].

If you have a two-dimensional array you can extract columns with colon indexing though.

julia> A = [1 2;2 3;3 4]
3×2 Array{Int64,2}:
 1  2
 2  3
 3  4

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

#5

Dear Gunar,

Thanks for the additional info. What confused is that, as you say, the colon indexing can be used to extract columns in a two-dimensional array, A[:,1], so naively I thought/hoped that A[:][1] would do something similar for an array of tuples. I still have problems really grasping the interplay (or non-interplay) of tuples and arrays. The original cause of my problem is that a function returning multiple values automatically returns them in a tuple. If they would be returned in an array, nothing of this would happen.

Thank,

Jacques


#6

A missing piece of information might be that repeated brackets isn’t a special syntax but just repeated indexing, as witnessed by quoting it.

julia> :(A[i][j])
:((A[i])[j])

If you have control of the function you can choose to package your multiple values in a vector before returning them.

julia> f(i) = [i, i + 1]
f (generic function with 1 method)

julia> A = f.([1, 2, 3])
3-element Array{Array{Int64,1},1}:
 [1, 2]
 [2, 3]
 [3, 4]

This doesn’t in itself make it easier to slice out a part than with a vector of tuples but makes it a little more convenient to repackage it in an array, that can be sliced.

julia> B = reduce(hcat, A)
2×3 Array{Int64,2}:
 1  2  3
 2  3  4

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

However, you are probably better off with some kind of dot vectorization or comprehension solution like David proposed.


#7

Returning an array would be much more expensive than returning a tuple, as an array has a significant amount of overhead compared to a tuple (which can be exactly zero-overhead in many circumstances), which is why it’s not done. Also, I’m not convinced that that would help in your case anyway, as a vector-of-vectors is still a completely different structure than a 2D matrix, and something like A[:][1] would not work even if each element of A were a vector.

Maybe you can describe more about what the actual problem you’re trying to solve is?


#8

Dear Robin,

Thanks for taking the time to replying to this. I understand your point about an array of arrays not being a matrix. The actual problem is that I have a function f(s) computing various measurements for a specific parameter value s. I am calling this function on an array of parameter values using f.(s_array). What I want to end up with is to have several arrays of measurements, which will effectively hold m1(s_array), m2(s_array), … where m1,m2 are my measurements.

What I could is to define a struct I guess, but I wanted to avoid this complication. Or as Gunnar just wrote, I could also put the measurement in an array in the function and then return the array. In the latter case I will end up with an array of arrays, while in the former case I would end up with an array of structs… not sure if that is any better. At the end of the day I want to plot each measurement versus the parameter values. Seems like a very standard problem.

Cheers,

Jacques


#9

I see, thanks. One easy solution is what @dpsanders suggested above and using a comprehension:

julia> function f(x)
         x, 2x
       end
f (generic function with 1 method)

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

julia> ys = f.(xs)
3-element Array{Tuple{Int64,Int64},1}:
 (1, 2)
 (2, 4)
 (3, 6)

julia> [y[1] for y in ys]
3-element Array{Int64,1}:
 1
 2
 3

julia> [y[2] for y in ys]
3-element Array{Int64,1}:
 2
 4
 6

this should be pretty efficient and easy to generalize. If you don’t want to use the comprehension syntax, you can also broadcast the getindex function (that’s the function that’s called when you do foo[bar]):

julia> getindex.(ys, 1)
3-element Array{Int64,1}:
 1
 2
 3

julia> getindex.(ys, 2)
3-element Array{Int64,1}:
 2
 4
 6

If you want to get really fancy, there’s one more trick you can try, as long as all of your returned values are of the same type and that type is isbits (so, a primitive immutable type like Float64 or a struct or tuple made up of primitive immutables). If all of your parameter values are Float64 or Int, for example, then this will work. All we have to do is rely on the fact that isbits types are stored inline in arrays, and we can freely transform an array-of-tuples into a matrix with reinterpret():

julia> r = reinterpret(Int, ys, (2, 3))
2×3 Array{Int64,2}:
 1  2  3
 2  4  6

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

julia> r[2, :]
3-element Array{Int64,1}:
 2
 4
 6

Edit: be warned that it’s easy to forget the column-major layout order and get the reinterpret size wrong. I had to edit this post because I got it wrong myself!

Which of these is appropriate will depend on your particular needs, and I suggest using https://github.com/JuliaCI/BenchmarkTools.jl to determine which performs best for your case.


#10

Dear Robin,

Thanks for the detailed explanation. To me it seems that the getindex function gets really close to what I was originally looking for!

The reinterpret won’t work in this case because the return values are of different types (floats and complex).

Cheers,

Jacques