Simple syntax for modifying specific entries of one Array based on indices from another Array?

How would I do this? It seems like there should be some simple syntax for this but if so, I can’t find it.

For example, suppose I have a 10x10 matrix A, and a 5x2 matrix idx, and the rows of idx represent row, column indices into A.

A = rand(0:9, (10, 10))
indices = [1 1; 2 4; 7 7]

I’d like to perform some operation (in-place, ideally) on those specific indices of A. I’ve noticed that I can use comprehensions:

[A[i,j] = 42 for (i,j) in eachrow(indices)]

But it feels a little strange to be doing assignment in the comprehension like that (maybe my Python bias is showing), and I also wonder about performance (though that’s not really a significant concern for me for the moment; this is just for a toy problem and for the moment I’m looking more for conciseness/expressiveness).

Is it possible to do something more like a view but with an arbitrary set of indices? I.e. something like

(@view_at_indices A indices) .= 42

More generally is there a way to combine views so that the combination can be treated identically to a regular view? For example, if you have some number of rectangular regions of an image that should all be operated on identically in some fashion.

How about this for the first part of your question:

A[CartesianIndex.(Tuple.(eachrow(indices)))] .= 42

You can create views with the same syntax, but hcat of views makes a copy of the data. I think it you want to combine views, you are better off combining indices and then creating one view.

3 Likes

Perhaps a little easier:

A[CartesianIndex.(eachcol(indices)...)] .= 42

Or, with a view, but not any better I think:

B = view(A, CartesianIndex.(eachcol(indices)...))
B .= 42
3 Likes

To complement the helpful answers above: in Julia, it is more natural (and performant) to store those indexes directly in a Vector of CartesianIndexes.

3 Likes

Agreed.

To answer the question directly: “Simple syntax for modifying specific entries of one Array based on indices from another Array?” The simple syntax for doing this already exists:

A[ind] .= 42

It’s just that ind has to contain actual indices, linear or cartesian. The indices variable that was provided in the OP didn’t actually contain indices, but was just a 2D array of integers, that could be interpreted as indices. But in order to get to the ‘simple syntax’ it is necessary to either make that conversion, or make indices have the right type in the first place.

3 Likes

You can also reinterpret the rows into CartesianIndex{2} (in general, CartesianIndex{N} for array dimension N)

A[reinterpret(CartesianIndex{2}, indices')] .= 1000

To my knowledge, this should be the fastest way to do it.

julia> @btime let B = view($A, CartesianIndex.(eachcol($indices)...))
       B .= 42
       end;
  2.948 μs (10 allocations: 640 bytes)

julia> @btime $A[CartesianIndex.(Tuple.(eachrow($indices)))] .= 42;
  1.487 μs (15 allocations: 1008 bytes)

julia> @btime $A[reinterpret(CartesianIndex{2}, $indices')] .= 1000
  54.819 ns (0 allocations: 0 bytes)
4 Likes