Conditional assignment syntax for array of vectors - julia

I'm new to Julia, coming more from Matlab and Python.
I cannot understand the Julia Syntax for conditional assignment of an array of vectors. To me, it appears to be inconsistent with the conditional assignment of an array of numbers.
For an array of numbers I can do the following:
a = [1,2,3]
b = [0,1,1]
a[b.>0] .= 5
This replaces the two elements of a with the new value 5.
My actual working example is with an array with elements of type Array{Int64,1}:
a = [[1,0,0], [0,1,0], [0,0,1]]
b = [0,1,1]
a[b.>0] .= Int64[0,0,5]
This does not work, output is:
ERROR: DimensionMismatch("array could not be broadcast to match destination")
However, this line works:
a[b.>0] .= [Int64[0,0,5]]
I cant understand this, as the element-wise assignment (.=) makes even less sense to me in the latter case, as the two arrays on the left and right have different sizes.
Can someone give an explanation?
Thanks in advance.

The operation:
x .= y
tries to iterate over x and y and performs the assignment. A simple case is:
julia> x = [1,2,3,4]
4-element Array{Int64,1}:
1
2
3
4
julia> y = [12,14]
2-element Array{Int64,1}:
12
14
julia> x[[2,4]] .= y
2-element view(::Array{Int64,1}, [2, 4]) with eltype Int64:
12
14
julia> x
4-element Array{Int64,1}:
1
12
3
14
We see that the left and right hand side have 2 elements so that an in-place assignment can be performed.
Then Julia has a special rule that if a container in right hand side has length 1 it can be expanded to match the size of the left hand side (this also works in higher dimensions than 1, but let us focus on a simple case).
You you for example have:
julia> x = [1,2,3,4]
4-element Array{Int64,1}:
1
2
3
4
julia> x[[2,4]] .= 11
2-element view(::Array{Int64,1}, [2, 4]) with eltype Int64:
11
11
julia> x
4-element Array{Int64,1}:
1
11
3
11
julia> length(11)
1
julia> x[[2,4]] .= [11]
2-element view(::Array{Int64,1}, [2, 4]) with eltype Int64:
11
11
julia> x
4-element Array{Int64,1}:
1
11
3
11
julia> length([1])
1
A crucial thing to note here is that [1] and 1 behave exactly the same in this case as a number is considered like a 1-element container holding this number in broadcasting.
Now going to your examples:
a = [[1,0,0], [0,1,0], [0,0,1]]
b = [0,1,1]
a[b.>0] .= Int64[0,0,5]
fails because:
julia> length(a[b.>0])
2
julia> length(Int64[0,0,5])
3
and we see that the dimensions do not match.
However in:
a[b.>0] .= [Int64[0,0,5]]
you have:
julia> length([Int64[0,0,5]])
1
So a container having length one gets expanded.
Note, however, that most likely you do not want to do a[b.>0] .= [Int64[0,0,5]] assignment as then a will hold the same array Int64[0,0,5]. E.g.
julia> a = [[1,0,0], [0,1,0], [0,0,1]]
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
julia> b = [0,1,1]
3-element Array{Int64,1}:
0
1
1
julia> a[b.>0] .= [Int64[0,0,5]]
2-element view(::Array{Array{Int64,1},1}, [2, 3]) with eltype Array{Int64,1}:
[0, 0, 5]
[0, 0, 5]
julia> a
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[0, 0, 5]
[0, 0, 5]
julia> a[2][1] = 100
100
julia> a
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[100, 0, 5]
[100, 0, 5]
and in most cases this is not what you want. A safer approach would be do to for example a for loop like this:
julia> a = [[1,0,0], [0,1,0], [0,0,1]]
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
julia> b = [0,1,1]
3-element Array{Int64,1}:
0
1
1
julia> for i in axes(b, 1)
b[i] > 0 && (a[i] = Int64[0,0,5])
end
julia> a
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[0, 0, 5]
[0, 0, 5]
julia> a[2][1] = 100
100
julia> a
3-element Array{Array{Int64,1},1}:
[1, 0, 0]
[100, 0, 5]
[0, 0, 5]
and you can see that each entry of a is a distinct object.

Logical indexing only selects those elements for which the condition is true. In your case of a[b.>0], that selects two elements:
julia> a[b.>0]
2-element Array{Int64,1}:
2
3
You are attempting to assign three elements into those two locations:
julia> a[b.>0] .= [10,20,30]
ERROR: DimensionMismatch("array could not be broadcast to match destination")
What you can do is also subset the values array you're assigning with the same conditional logic to pick which two elements should be assigned:
julia> a[b.>0] .= [10,20,30][b.>0]
2-element view(::Array{Int64,1}, [2, 3]) with eltype Int64:
20
30
julia> a
3-element Array{Int64,1}:
1
20
30
The syntax a[b.>0] .= [Int64[0,0,5]] will only work if a is an Any array, and it means something completely different. It broadcasts the values array itself into all the selected locations — that is, it puts the whole array as repeated elements in a!
julia> a = Any[1,2,3]
3-element Array{Any,1}:
1
2
3
julia> a[b.>0] .= [Int64[0,0,5]]
2-element view(::Array{Any,1}, [2, 3]) with eltype Any:
[0, 0, 5]
[0, 0, 5]
julia> a
3-element Array{Any,1}:
1
[0, 0, 5]
[0, 0, 5]

Related

Converting a n-element Vector{Vector{Int64}} to a Vector{Int64}

I have a list of vectors (vector of vectors) like the following:
A 2-element Vector{Vector{Int64}}: A= [347229118, 1954075737, 6542148346,347229123, 1954075753, 6542148341] [247492691, 247490813, -2796091443606465490, 247491615, 247492910, 247491620, -4267071114472318843, 747753505]
the goal is to have them all in just one vector. I did try collect, A[:], vec(A), flatten(A) but it still returns 2-element Vector{Vector{Int64}}
I don't know what command I should use. Is there anything
Assuming your input data is:
julia> x = [[1, 2], [3, 4], [5, 6]]
3-element Vector{Vector{Int64}}:
[1, 2]
[3, 4]
[5, 6]
here are some natural options you have.
Option 1: use Iterators.flatten:
julia> collect(Iterators.flatten(x))
6-element Vector{Int64}:
1
2
3
4
5
6
You can omit collect in which case you get a lazy iterator over the source data which is more memory efficient.
Option 2: use vcat:
julia> reduce(vcat, x)
6-element Vector{Int64}:
1
2
3
4
5
6
You could also write:
julia> vcat(x...)
6-element Vector{Int64}:
1
2
3
4
5
6
but splatting might get problematic if your x vector is very long. In which case I recommend you to use the reduce function as shown above.

Create a Vector of Integers and missing Values

What a hazzle...
I'm trying to create a vector of integers and missing values. This works fine:
b = [4, missing, missing, 3]
But I would actually like the vector to be longer with more missing values and therefore use repeat(), but this doesn't work
append!([1,2,3], repeat([missing], 1000))
and this also doesn't work
[1,2,3, repeat([missing], 1000)]
Please, help me out, here.
It is also worth to note that if you do not need to do an in-place operation with append! actually in such cases it is much easier to do vertical concatenation:
julia> [[1, 2, 3]; repeat([missing], 2); 4; 5] # note ; that denotes vcat
7-element Array{Union{Missing, Int64},1}:
1
2
3
missing
missing
4
5
julia> vcat([1,2,3], repeat([missing], 2), 4, 5) # this is the same but using a different syntax
7-element Array{Union{Missing, Int64},1}:
1
2
3
missing
missing
4
5
The benefit of vcat is that it automatically does the type promotion (as opposed to append! in which case you have to correctly specify the eltype of the target container before the operation).
Note that because vcat does automatic type promotion in corner cases you might get a different eltype of the result of the operation:
julia> x = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> append!(x, [1.0, 2.0]) # conversion from Float64 to Int happens here
5-element Array{Int64,1}:
1
2
3
1
2
julia> [[1, 2, 3]; [1.0, 2.0]] # promotion of Int to Float64 happens in this case
5-element Array{Float64,1}:
1.0
2.0
3.0
1.0
2.0
See also https://docs.julialang.org/en/v1/manual/arrays/#man-array-literals.
This will work:
append!(Union{Int,Missing}[1,2,3], repeat([missing], 1000))
[1,2,3] creates just a Vector{Int} and since Julia is strongly typed the Vector{Int} cannot accept values of non-Int type. Hence, when defining a structure, that you plan to hold more data types within, you need to explicitly state it - here we have defined Vector{Union{Int,Missing}}.

Vectorized splatting

I'd like to be able to splat an array of tuples into a function in a vectorized fashion. For example, if I have the following function,
function foo(x, y)
x + y
end
and the following array of tuples,
args_array = [(1, 2), (3, 4), (5, 6)]
then I could use a list comprehension to obtain the desired result:
julia> [foo(args...) for args in args_array]
3-element Array{Int64,1}:
3
7
11
However, I would like to be able to use the dot vectorization notation for this operation:
julia> foo.(args_array...)
ERROR: MethodError: no method matching foo(::Int64, ::Int64, ::Int64)
But as you can see, that particular syntax doesn't work. Is there a vectorized way to do this?
foo.(args_array...) doesn't work because it's doing:
foo.((1, 2), (3, 4), (5, 6))
# which is roughly equivalent to
[foo(1,3,5), foo(2,4,6)]
In other words, it's taking each element of args_array as a separate argument and then broadcasting foo over those arguments. You want to broadcast foo over the elements directly. The trouble is that running:
foo.(args_array)
# is roughly equivalent to:
[foo((1,2)), foo((3,4)), foo((5,6))]
In other words, the broadcast syntax is just passing each tuple as a single argument to foo. We can fix that with a simple intermediate function:
julia> bar(args) = foo(args...);
julia> bar.(args_array)
3-element Array{Int64,1}:
3
7
11
Now that's doing what you want! You don't even need to construct the second argument if you don't want to. This is exactly equivalent:
julia> (args->foo(args...)).(args_array)
3-element Array{Int64,1}:
3
7
11
And in fact you can generalize this quite easily:
julia> splat(f) = args -> f(args...);
julia> (splat(foo)).(args_array)
3-element Array{Int64,1}:
3
7
11
You could zip the args_array, which effectively transposes the array of tuples:
julia> collect(zip(args_array...))
2-element Array{Tuple{Int64,Int64,Int64},1}:
(1, 3, 5)
(2, 4, 6)
Then you can broadcast foo over the transposed array (actually an iterator) of tuples:
julia> foo.(zip(args_array...)...)
(3, 7, 11)
However, this returns a tuple instead of an array. If you need the return value to be an array, you could use any of the following somewhat cryptic solutions:
julia> foo.(collect.(zip(args_array...))...)
3-element Array{Int64,1}:
3
7
11
julia> collect(foo.(zip(args_array...)...))
3-element Array{Int64,1}:
3
7
11
julia> [foo.(zip(args_array...)...)...]
3-element Array{Int64,1}:
3
7
11
How about
[foo(x,y) for (x,y) in args_array]

What is the julia equivalent of np.indices in numpy?

In Python, there is np.indices, which returns the indices of a grid:
Python 2.7.1
> import numpy as np
> x,y = np.indices((2,2))
> x
array([[0, 0],
[1, 1]])
> y
array([[0, 1],
[0, 1]])
What would be a similar function in Julia? Especially for multidimensional grids.
I tried eachindex, but it expects a grid as input, not just the dimensions. Also the output is a flat list of linear indices, instead of separated cartesian components.
It can be obtained using the CartesianIndices function:
julia> inds = CartesianIndices((2,2))
2×2 CartesianIndices{2,Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}}:
CartesianIndex(1, 1) CartesianIndex(1, 2)
CartesianIndex(2, 1) CartesianIndex(2, 2)
julia> Tuple.(inds)
2×2 Array{Tuple{Int64,Int64},2}:
(1, 1) (1, 2)
(2, 1) (2, 2)
julia> getindex.(inds, 1)
2×2 Array{Int64,2}:
1 1
2 2
julia> getindex.(inds, 2)
2×2 Array{Int64,2}:
1 2
1 2

Circular permutations

Given a vector z = [1, 2, 3], I want to create a vector of vectors with all circular permutations of z (i.e. zp = [[1,2,3], [3,1,2], [2,3,1]]).
I can print all elements of zp with
for i in 1:length(z)
push!(z, shift!(z)) |> println
end
How can I store the resulting permutations? Note that
zp = Vector(length(z))
for i in 1:length(z)
push!(z, shift!(z))
push!(zp, z)
end
doesn't work as it stores the same vector z 3 times in zp.
One way would just be to copy the vector before pushing it:
z = [1, 2, 3];
zp = Vector();
for i in 1:length(z)
push!(z, shift!(z))
push!(zp, copy(z))
end
gives me
julia> zp
3-element Array{Any,1}:
[2,3,1]
[3,1,2]
[1,2,3]
But I tend to prefer avoiding mutating operations when I can. So I'd instead write this as
julia> zp = [circshift(z, i) for i=1:length(z)]
3-element Array{Array{Int64,1},1}:
[3,1,2]
[2,3,1]
[1,2,3]
This seems to execute pretty quick on my machine (faster than a comprehension):
julia> z=[1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> zp=Vector{typeof(z)}(length(z))
3-element Array{Array{Int64,1},1}:
#undef
#undef
#undef
julia> for i=1:length(z)
zp[i]=circshift(z,i-1)
end
julia> zp
3-element Array{Array{Int64,1},1}:
[1,2,3]
[3,1,2]
[2,3,1]
julia>

Resources