I have a struct which should behave like a matrix. I want to have all the standard indexing methods julia
has, like A[:, j]
, A[1:3, j]
, A[[1,3,4], j]
and so on.
Currently I have the following (simplified)
function Base.getindex(A::myMatrix, i::Int, j::Int)
#Doing some stuff
A(i, j)
end
Should I implement Base.getindex(A::myMatrix, I::Vector, j::Int)
and Base.getindex(A::myMatrix, I::Range, j::Int)
and the similar versions for j
?
Or is there a simple way to make the standard indexing available for a custom struct?
Thank you.
Check the section on the AbstractArray
interface. So long as myMatrix <: AbstractArray
(or in your case, likely <: AbstractMatrix
), the interface page tells you that you’ll get array/range indexing automatically. This will be implemented by looping over the scalar indexing you already defined.
If there are significant performance savings to be had from batching these multi-index accesses, you may consider implementing specialized methods for doing so. Exactly what to implement and for what input types is up to you and depends on the sort of savings you’re after.
If such batching efficiencies truly exist, I’d start with adding a Base.getindex(A::myMatrix, i, j)
method with untyped i
and j
and write it generically for that. You can continue to add increasingly specialized methods from there, until you decide there’s no longer significant benefit. In very few cases will the types of i
or j
need to be more specialized than AbstractVector
.
2 Likes
Thank you! I got the following
struct myMatrix <: AbstractMatrix{Float64}
# definitions
m::Int
n::Int
end
Base.size(M::myMatrix) = (M.m, M.n)
Base.getindex(M::myMatrix, i::Int, j::Int) = #some stuff
This seems to work. The manual mentions Base.IndexStyle()
. What is this for? Is necessary to implement this?
An AbstractArray
in Julia is allowed to have one of two preferred indexing schemes: Cartesian or Linear. IndexLinear()
is efficient when applicable and applies to normal Julia Array
types because of the way they’re stored in memory (the memory is snaked through the columns to form the array), but IndexCartesian()
is more general.
julia> collect(CartesianIndices(zeros(2,3)))
2×3 Matrix{CartesianIndex{2}}:
CartesianIndex(1, 1) CartesianIndex(1, 2) CartesianIndex(1, 3)
CartesianIndex(2, 1) CartesianIndex(2, 2) CartesianIndex(2, 3)
julia> collect(LinearIndices(zeros(2,3)))
2×3 Matrix{Int64}:
1 3 5
2 4 6
As you can see, the LinearIndices
of an array are of the form a:b
(but snake through the columns) while the CartesianIndices
represent each coordinate explicitly.
Julia tries to access arrays using their preferred mode (since one is usually more efficient than the other). IndexCartesian()
arrays implement getindex(x,coordinate1,coordinate2,coordinate3...)
while IndexLinear()
arrays implement getindex(x,linearindex)
. Julia will transform indexing operations to the preferred modality.
Since you wrote the method Base.getindex(::myMatrix, ::Int, ::Int)
, it sounds like you want Base.IndexStyle(::Type{<:myMatrix}) = IndexCartesian()
. The reason you didn’t have write this is that it’s the default.
2 Likes
Thank you for such a detailed explanation! I learned a lot.