Destructuring assignment with arrays, like in NumPy

Hi!

So, for an example, I have a 110x2 delimited text file on disk. The first column is my variable Y, and the second column is my variable X. My goal: get Y and X both in their own array, either column or row, no problem.

In Python, I can do something really nice:

import numpy as np
y, x = np.loadtxt("abc.txt").T

In Julia, the destructuring assignment does not seem as general, so I have to do:

using DelimitedFiles
yx = readdlm("abc.txt", Float64)
y, x = yx[:, 1], yx[:, 2]

My question is: in Python, I can understand what destructuring does. There’s a whole PEP about it, and the idea is to call iter(...) on the RHS and consume the elements on the left hand side following some rules.

For Julia, the only documentation I can find about destructuring is on the Functions · The Julia Language page, and that deals only with tuples. But it seems like more general destructuring is possible (???).

I’m wondering, is there anything written about destructuring in Julia? How does it work? Would it be possible to emulate Python’s behavior, or reproduce the Python example above?

1 Like

Any object that iterates can be destructured on assignment. You can see a draft I wrote for the docs here: https://github.com/JuliaLang/julia/pull/30579/files (you’ve motivated me to polish it up, any thoughts or feedback would be appreciated).

In numpy, iterating over an n-dimensional array iterates over (n-1)-dimensional arrays. In Julia, iterating over an array iterates over the elements. There is eachcol/eachrow which gives you an iterator over each column/row:

julia> A = rand(3,2)
3×2 Array{Float64,2}:
 0.607359  0.239138
 0.695322  0.581047
 0.774758  0.333456

julia> y,x = eachcol(A)
Base.Generator{Base.OneTo{Int64},Base.var"#179#180"{Array{Float64,2}}}(Base.var"#179#180"{Array{Float64,2}}([0.6073594183138431 0.23913800932018425; 0.695321894908304 0.5810467945907654; 0.7747577825874581 0.33345565551241685]), Base.OneTo(2))

julia> y
x3-element view(::Array{Float64,2}, :, 1) with eltype Float64:
 0.6073594183138431
 0.695321894908304
 0.7747577825874581

julia> x
3-element view(::Array{Float64,2}, :, 2) with eltype Float64:
 0.23913800932018425
 0.5810467945907654
 0.33345565551241685

Note that x and y here are views, not copies like numpy. To get that you can broadcast copy over the iterator:

julia> y,x = copy.(eachcol(A))
2-element Array{Array{Float64,1},1}:
 [0.6073594183138431, 0.695321894908304, 0.7747577825874581]
 [0.23913800932018425, 0.5810467945907654, 0.33345565551241685]

julia> y
x3-element Array{Float64,1}:
 0.6073594183138431
 0.695321894908304
 0.7747577825874581

julia> x
3-element Array{Float64,1}:
 0.23913800932018425
 0.5810467945907654
 0.33345565551241685
2 Likes

Thank you @simonbyrne, that’s perfect.

I wish Julia could a, b..., c = 1:10!

See https://github.com/JuliaLang/julia/issues/2626 for discussion on that issue. Given that it is now 7 years old, I wouldn’t hold my breath on it though (though it is still under active debate, so maybe Julia 2.0 :crossed_fingers:)

2 Likes

Re: the documentation you have produced, I think it’s great, and the link to the iteration interface helped me out. I hope your doc change can be merged, maybe it will help others too. (My only question is why it lives with the documentation for functions instead of, well maybe variables? but that is a small detail.)

1 Like