# Elegant way to do multi-index algebra

I want to use a number to represent a multi-index. Consider `i` in `1:3` and `j` in `1:3`. I want `3` to represent for `(0,3)`, `4` for `(1,1)`, `8` for `(3,2)` and so forth.

This can be done by `i = (num-1)÷3 + 1` and `j = (num-1)%3 + 1`. This seems not quite elegant. Is there a better way to do this job? This is the first time I think 0-based indexing is superior…

Julia already supports linear indexing of multidimensional arrays.

Note that the `i = (num-1)÷3 + 1 and j = (num-1)%3 + 1` formula you gave is row-major but Julia is column-major. If you really care about this you can just reverse the index order or transpose the array.

``````julia> A = [(i,j) for i=1:3, j=1:3]
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 1)  (1, 2)  (1, 3)
(2, 1)  (2, 2)  (2, 3)
(3, 1)  (3, 2)  (3, 3)

julia> A[3], A[4], A[8]
((3, 1), (1, 2), (2, 3))
``````

You can get both the `(i,j) -> n` and `n -> (i,j)` maps without allocation (as array-like objects that compute elements on the fly) using `LinearIndices` and `CartesianIndices`, respectively:

``````julia> C = CartesianIndices((3, 3)) # indices of a 3x3 array
CartesianIndices((3, 3))

julia> collect(C) # collect and display all the elements explicitly
3×3 Matrix{CartesianIndex{2}}:
CartesianIndex(1, 1)  CartesianIndex(1, 2)  CartesianIndex(1, 3)
CartesianIndex(2, 1)  CartesianIndex(2, 2)  CartesianIndex(2, 3)
CartesianIndex(3, 1)  CartesianIndex(3, 2)  CartesianIndex(3, 3)

julia> C[3], C[4], C[8]
(CartesianIndex(3, 1), CartesianIndex(1, 2), CartesianIndex(2, 3))

julia> L = LinearIndices(C) # alternatively: L = LinearIndices((3, 3))
3×3 LinearIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}:
1  4  7
2  5  8
3  6  9

julia> L[3,1], L[1,2], L[2,3]
(3, 4, 8)
``````
3 Likes

Thanks for your answer. I know `julia` has linear indexing for multidimensional arrays, but I’m working on a custom type.

The trick using an index matrix is interesting, but the size of my type is not fixed. That means I need to generate a new matrix when the sized is changed. I will think about how to deal with this.

Thank you!

`CartesianIndices` and `LinearIndices` aren’t “index matrices” and they don’t need to be “generated” — creating them is essentially free, because they really just store the size of the array:

``````julia> dump(L)
LinearIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}
indices: Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}
1: Base.OneTo{Int64}
stop: Int64 3
2: Base.OneTo{Int64}
stop: Int64 3

julia> dump(C)
CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}
indices: Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}
1: Base.OneTo{Int64}
stop: Int64 3
2: Base.OneTo{Int64}
stop: Int64 3
``````

and the indices are calculated only when you do `L[i,j]` or `C[i]`. Internally, they are doing calculations much like the one you suggested.

They are array-like objects, similar to ranges `1:n`, that act like arrays but don’t store their elements.

8 Likes

Thank you for the explanation!