Vector with negative indices extending getindex

Hello. I’m trying to extend the method(s) for get index so to have the ability of accessing a numeric vector, Vector{T} where T<:Number, at a negative (or zero) index and get zero(T).

The intended behaviour is something like:

julia> v = [1,2,3,4];

julia> v[1]
1

julia> v[-2]
0

I considered using a combination of PaddedViews and OffsetArrays, but that would be cumbersome, as I don’t know a priori the maximum value of the negative indices I might use.

Hence, the solution seems to be extending the Base.getindex() (not necessarily setindex!() as I don’t intend to assign any value in those indices).

I naively tried with (don’t try the following, as it crashes julia)

import Base: getindex
getindex(v::V,i::Int) where Vector{T} where T<:Number = i > 0 ? Base.getindex(v,i) : zero(T)

This causes a stack overflow error and crashes Julia. I believe I understand why (it is causing an infinite recursion, as it is overwriting the method ALSO for positive i).

How would you fix this? Can I distinguish by type between positive and negative (or zero) values of i? Is there some other obvious way of obtaining the same behaviour that I don’t see?

PS I’m trying to avoid to define new types (but that might be the error in my approach).

Yeah, that’s not possible. You’ve either got to define a new type or use a different function. I’d do the latter:

get(A, i) = i <= 0 ? zero(eltype(A)) : A[i]

Custom array types in Julia don’t demand much, but they do demand that you know their size.

2 Likes

In fact, the suggested function already exists in Base (in some capacity)

julia> A = 11:15
11:15

julia> get(A,1,0) # returns A[1]
11

julia> get(A,-3,zero(eltype(A))) # A[-3] is not defined so returns `zero(eltype(A))`
0

get is generally used to access a particular index/key from a collection or to return the default if the index/key is not present. But you’ll have to write your own (as in the above suggestion) if you still want it to error for too-big indices.

By the way, your initial attempt to overload Base.getindex is an example of type piracy and is very dangerous for the reasons you discovered. It changes how the function works everywhere, which is a recipe for chaos. Even if it didn’t produce an error, it might break functionality in more subtle ways (which is even worse because it may be hard to notice).

But if you need this to apply to getindex or its syntax (i.e., A[i]), specifically, and can’t simply use a different access function (like get or your own version), then you’ll need to define a new type of array to do it safely. Making simple types is usually fairly easy. In this case, you may want to reference and follow the array interface.

2 Likes

A good way might be to leverage an existing implementation (sometimes it is hard to find, but often exists for useful stuff):

julia> using ImageFiltering

julia> v = [1,2,3,4];

julia> vv = BorderArray(v, Fill(0, (50,)));

julia> vv[1]
1

julia> vv[-2]
0

julia> size(vv)
(104,)

This implementation comes from the mature Images eco-system. The size, as you can see, includes the padding, but it doesn’t take up memory.

This requires to know in advance the desired length of the padding (and does change the length of the passed view).

But I will take a look at how they implement it.