Please someone explain me the array indexing example from manual?

I am truly lost at following example. I appreciate any directions, I seem to understand all previous examples, but this one just made me dazzle.

Julia 1.10 manual array indexing section

If I_1 is changed to a two-dimensional matrix, then X becomes an n+1-dimensional array of shape (size(I_1, 1), size(I_1, 2), length(I_2), …, length(I_n)). The matrix adds a dimension.

julia> A = reshape(collect(1:16), (2, 2, 2, 2));

julia> A[[1 2; 1 2]]
2×2 Matrix{Int64}:
 1  2
 1  2

julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Matrix{Int64}:
 5  6
 5  6

The location i_1, i_2, i_3, …, i_{n+1} contains the value at A[I_1[i_1, i_2], I_2[i_3], …, I_n[i_{n+1}]]. All dimensions indexed with scalars are dropped. For example, if J is an array of indices, then the result of A[2, J, 3] is an array with size size(J). Its jth element is populated by A[2, J[j], 3].

1 Like

I think the key point of the example is to see that one can access multidimensional arrays in two ways:

  • via a linear index (think: one integer)
  • via Cartesian indices for each dimension (here: four numbers

For example A[1,2,1,1] is the same as A[3]. (Linear indices are column-major in Julia…)

Now, what is done in the tutorial is now to show: All rules about evaluating a one dimensional vector translate to rules for evaluating multidimensional arrays. For example, we can input a matrix of indices as linear indices and then the output is A evaluated at the (four) indices given.

Equally, one can input a matrix of indices into any of the four positions for the Cartesian indices and then it will also evaluate the matrix with those variations if Cartesian indices.

3 Likes

It might make a bit more sense with a bit more labeling, a slightly more distinctive array shape, and a little bit of a slower ramp:

julia> A = reshape(collect(1:12), (4, 3))
4×3 Matrix{Int64}:
 1  5   9
 2  6  10
 3  7  11
 4  8  12

julia> chosen_row = 2; chosen_col = 3;

julia> A[chosen_row, chosen_col]
10

Now if you select more than one row at a time, you’ll get a vector:

julia> chosen_rows = [2,3]
2-element Vector{Int64}:
 2
 3

julia> A[chosen_rows, chosen_col]
2-element Vector{Int64}:
 10
 11

That vector is like doing two scalar indexing expressions and putting them back in the appropriate place: [A[2, 3], A[3, 3]].


Now here’s the magic that makes that example tricky — what if chosen_rows were a matrix of row indices?

julia> chosen_rows = [2; 3;; 4; 1]
2×2 Matrix{Int64}:
 2  4
 3  1

What does A[chosen_rows, chosen_col] return? It returns a matrix! And that matrix is equivalent to doing four scalar indexings at the matching place with the index matrix.

julia> A[chosen_rows, chosen_col]
2×2 Matrix{Int64}:
 10  12
 11   9

julia> [A[2, 3]; A[3, 3];; A[4, 3]; A[1, 3]]
2×2 Matrix{Int64}:
 10  12
 11   9

That matrix is all row indexes — but its shape just determines the shape of the output. It’s also equivalent to doing A[[2, 3, 4, 1], 3] and then reshaping that output to a 2x2 matrix.

Does that help?

14 Likes

Excellent example, thank you for the effort. I believe, I understood the example you provided but still not the example I asked. Could you elaborate the example provided in the manual, that I have provided in the question. The array A is four dimensional, the 2x2 matrix is being used as index, as far as I understand, based on your example, the first dimension of index matrix [1,2] is for selecting elements in the first dimension of the A, the second [1,2] for the second etc. There are no third, or fourth indexes provided for the third and fourth dimensions of A in the example. Are there implicit defaults for the third or fourth dimensions. I could not correlate following.

julia> A = reshape(collect(1:16), (2, 2, 2, 2));
2×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 1  3
 2  4

[:, :, 2, 1] =
 5  7
 6  8

[:, :, 1, 2] =
  9  11
 10  12

[:, :, 2, 2] =
 13  15
 14  16

julia> A[[1 2; 1 2]]
2×2 Matrix{Int64}:
 1  2
 1  2

Scroll code down to see entire snippet

function array()
    A = reshape(collect(11:26), (2, 2, 2, 2))
    return A
end

function idx()
    ix = [1 2; 1 2]
    return ix
end

array()

2×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 11  13
 12  14

[:, :, 2, 1] =
 15  17
 16  18

[:, :, 1, 2] =
 19  21
 20  22

[:, :, 2, 2] =
 23  25
 24  26

idx()
2×2 Matrix{Int64}:
 1  2
 1  2

array()[idx()] # 1-d index since other indices are not given; treat array as linearly indexed; result is returned in the shape of the index matrix
2×2 Matrix{Int64}:
 11  12
 11  12

array()[idx(), 1, 2, 1]   
# 4-d index; rows 1 and 2 of the: (1st column, 2nd slice, 1st super slice). Refer to array() output above.
2×2 Matrix{Int64}:
 15  16
 15  16

# play with array() and idx() function definitions to explore further
1 Like

The difference between A[[1 2; 1 2]] and A[[1 2; 1 2], 1, 2, 1] is exactly the same as A[2] vs A[2,1,2,1]. The first is using linear indexing to grab the second element in the array, whereas the second is grabbing the second row of the first column of the second “page” of the first “book” (to stretch the dimension names a bit).

It’s no different when you use vectors of indices for any of the dimensions.

See:

https://docs.julialang.org/en/v1/manual/arrays/#Number-of-indices