How to use custom indices

indexing

#1

Hello everyone, I’m having a hard time figuring out how to implement arrays with custom indices.
I’ve read all the existing documentation out there, but it’s often poor and lacking of information, especially examples.
I’m trying to implement a simple array (extending AbstractArray) with indices ranging between two custom values (‘lb’ and ‘ub’ below), which can also be negative numbers.

struct MyType{T} <: AbstractArray{T,1}
data::Array{T,1}
dim::Int #Number of components of ‘data’
lb::Int #lower bound of indices
ub::Int #Upper bound of indices
end

Now I’m really not sure how to redefine the array functions, in particular:

  1. Shall I redefine show? Some say I shouldn’t, but why?
  2. What about getindex() and setindex!()? No tutorial shows an example of their redefinition with custom indices, so I assume that they remain unchanged, but it seems strange.
    Maybe I should use ‘1+i-a.lb’ instead of ‘i’ as index of data?

Since I don’t know much about this, here’s the rest of my code until now, please suggest me what to change to make it work properly.

MyType{T}(::Type{T}, n::Int, lb::Int = 0) = MyType(Array{T,1}(n), n, lb, lb+n-1);
MyType(n::Int) = MyType(Float64, n);
Base.size(a::MyType) = (a.dim,)
Base.IndexStyle(::Type{<:MyType}) = IndexLinear()
Base.similar(a::MyType, ::Type{T}, d::Dims) where {T} = MyType(T, d[1])
Base.getindex(a::MyType, i::Int) = a.data[i]
Base.setindex!(a::MyType, v, i::Int) = (a.data[i] = v)
Base.indices(a::MyType) = map(URange, a.lb, a.ub)

Thanks in advance!


#2

The work’s already been done in OffsetArrays.jl

julia> using OffsetArrays

julia> w = OffsetArray(1:5, -2:2)
OffsetArrays.OffsetArray{Int64,1,UnitRange{Int64}} with indices -2:2:
 1
 2
 3
 4
 5

julia> w[0]
3

#3

Well, this solves my specific problem, so I’m likely to accept this answer.
Yet, I’d still like to know how to implement custom indices, because in the future I may work on something else that isn’t already solved (by OffsetArray).
For example, how would you modify my code to make it work? Is it a simple task? Thanks again!


#4

Your code looks very close, and I think that if you just do (as you suggest) overload getindex and setindex! to use 1 + i - a.lb then your type will pretty much just work:

struct MyType{T} <: AbstractArray{T,1}
    data::Array{T,1}
    dim::Int #Number of components of ‘data’
    lb::Int #lower bound of indices
    ub::Int #Upper bound of indices
end

MyType{T}(::Type{T}, n::Int, lb::Int = 0) = MyType(Array{T,1}(n), n, lb, lb+n-1);
Base.size(a::MyType) = (a.dim,)
Base.IndexStyle(::Type{<:MyType}) = IndexLinear()
Base.similar(a::MyType, ::Type{T}, d::Dims{1}) where {T} = MyType(T, d[1])
Base.getindex(a::MyType, i::Int) = a.data[1 + i - a.lb]
Base.setindex!(a::MyType, v, i::Int) = (a.data[1 + i - a.lb] = v)
Base.indices(a::MyType) = (a.lb:a.ub,)

Usage:

julia> m = MyType(Float64, 5, -2)
MyType{Float64} with indices -2:2:
 0.0
 0.0
 0.0
 0.0
 0.0

julia> m .= 0
MyType{Float64} with indices -2:2:
 0.0
 0.0
 0.0
 0.0
 0.0

julia> m[0] = 5
5

julia> m
MyType{Float64} with indices -2:2:
 0.0
 0.0
 5.0
 0.0
 0.0

julia> sum(m)
5.0

I also made a couple of very minor changes:

  • restricted your similar method to Dims{1}, since your type is one-dimensional and it doesn’t make sense to call similar(MyType, T, dims) with a multi-dimensional dims
  • simplified Base.indices to just a.lb:a.ub

#5

I think the best answer to this would be to look at the OffsetArray source code and go from there. As far as I know, you do need to define getindex and setindex! methods for your array type, both for integer indexing and cartesian indexing (Vararg{Int,N}).