First class "from end" indices into array

Is there any way to make a first class index that points to an element from the end of an array? So it can be e.g. assigned to a variable. For example, in python I can do

idx1 = 0
idx2 = -1
a = [1,2,3,4,5]
a[idx1] # Gives 1
a[idx2] # Gives 5

In Julia one can use a special syntax with end keyword to access arrays from the end, but end has its special meaning only inside square brackets. Is there some way, e.g. a special type FromEnd, so I can write

idx = FromEnd(1)
a = [1,2,3,4,5]
a[idx] # Gives 5

indices = CartesianIndex(1):CartesianIndex(FromEnd(1)) # Gives all indices but the last
julia> a = [1,2,3,4,5]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> a[end]
5

julia> a[end-(2-1)]
4

julia> a[end-(3-1)]
3

julia> fromend(arr,n)=arr[end-(n-1)]
fromend (generic function with 1 method)

julia> fromend(a,4)
2
1 Like

This is not what I want. In Python you do not have to know the array to create an index into that array. Please notice a difference between

a[-1]

and

a[fromend(a,1)]

In the first case a appears only once.

You can use the to_indices mechanism to do this:

julia> struct FromEnd
       i::Int
       end

julia> Base.to_indices(A, inds, I::Tuple{FromEnd, Vararg}) = (@inline; (last(inds[1])-I[1].i, to_indices(A, Base.safe_tail(inds), Base.tail(I))...))

julia> I = FromEnd(2)
FromEnd(2)

julia> arr = reshape(1:15, 5, 3)
5Ă—3 reshape(::UnitRange{Int64}, 5, 3) with eltype Int64:
 1   6  11
 2   7  12
 3   8  13
 4   9  14
 5  10  15

julia> arr[:,I]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> arr[I,:]
3-element Vector{Int64}:
  3
  8
 13

Edit: This is not a full solution, just a proof-of-concept.

2 Likes

The answer is no.
There is no firstclass negative index in Julia.
The best you can do is
a[end-(1-1)] for a[-1]
a[end-(2-1)] for a[-2]
a[end-(3-1)] for a[-3]

Cool, thanks. But since UnitRange has kind * → * (i.e. it has only one parameter), it’s fundamentally impossible to write something like x:FromEnd(y), isn’t it?

Well for this to work you’d need to hook into UnitRange and the like which likely will be very messy and doesn’t sound like a great idea to me. The problem is that a UnitRange is concept separate from indexing that doesn’t really make sense without a well-defined endpoint.

Edit: Ofc it’s not “fundamentally impossible” to do what you ask - the question is rather “How much effort do you need?”. For the mixed ranges to work, you probably need another type.

OK, thanks! Also, ranges like that are not supported in python either:

>>> 0:-1
  File "<stdin>", line 1
    0:-1
    ^
SyntaxError: illegal target for annotation

Are you OK with not using the getindex / [ ] syntax directly?

If yes, what about a variation of this (with a better name)?

function getindex_potentially_from_end(a, i)
    if i > 0
        return a[i]
    else
        return a[end + i]
    end
end

Of course this particular example probably only works with standard 1-based vectors, but it just requires one function and no extra types. Just as an idea.

It can be a solution, but it breaks Julia syntax for indexing :frowning: I like @abraemer 's solution more

1 Like

EndpointRanges.jl provides something like this:

julia> a = [1,2,3,4,5]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> a[iend]
5

julia> idx2 = iend
EndpointRanges.IEnd()

julia> a[idx2]
5
5 Likes

This is great!

When used as indices, the keywords begin and end desugar to firstindex and lastindex respectively.

We can confirm that ourselves with Meta.@lower:

julia> Meta.@lower begin
           vec[end]
       end
:($(Expr(:thunk, CodeInfo(
    @ REPL[7]:2 within `top-level scope`
1 ─ %1 = vec
│   %2 = Base.lastindex(vec)
│   %3 = Base.getindex(%1, %2)
└──      return %3
))))