Joachim Fischer
Joachim Fischer

Reputation: 43

Conditional assignment syntax for array of vectors

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.

Upvotes: 4

Views: 220

Answers (2)

Bogumił Kamiński
Bogumił Kamiński

Reputation: 69949

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.

Upvotes: 3

mbauman
mbauman

Reputation: 31362

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]

Upvotes: 3

Related Questions