In Python, I can get the the second-to-last value of an array using a nice syntax:

``````>>> ix = -2
>>> lst = [10, 20, 30, 40]
>>> lst[ix]
30
``````

but I can’t do this in Julia:

``````julia> lst = [10, 20, 30, 40];
julia> ix = end - 2
ERROR: syntax: unexpected "end"
julia> lst[ix]
``````

which means I need access to the array in order to specify the index location that I want.

I can do

``````julia> lastbutn(n) = a-> a[end-n]
julia> lastbutn(1)(lst)
30
``````

but this is awkward.

It looks like the `end-1` syntax only works inside of square brackets. Is there a way to make this easier?

1 Like

`lastindex(a)-2` is the equivalent to `end-2` outside of an indexing expression.

6 Likes

You can do it without needing a reference to `a` by making a struct called `Last` or something, and defining the appropriate methods (`Base.to_indices` among others). This is how Base’s `Colon()` works as well as InvertedIndices.jl’s `Not`. I played around with this a couple of years ago to make ModularIndices.jl which provides a `Mod` struct to do wrap-around indexing:

``````julia> using ModularIndices

julia> A = rand(3)
3-element Array{Float64,1}:
0.523471984061487
0.3975791533002422
0.3230510641200286

julia> A[Mod(4)]
0.523471984061487

julia> A[4]
ERROR: BoundsError: attempt to access 3-element Array{Float64,1} at index [4]
Stacktrace:
[1] getindex(::Array{Float64,1}, ::Int64) at ./array.jl:729
[2] top-level scope at none:0
``````

This is not very useful because as I later learned you can do `A[mod1(4, end)]` without a package to get this kind of behavior! (Although of course that syntax is only valid within the indexing expression, so the advantage of the package is that you can do `ind = Mod(4)` without a reference to the array, same as in the question here). But anyway, you could do the same thing with a `Last` struct so that say `Last(n)` would give `v[Last(n)] == v[end - n]`.

edit: I said the methods needed were "`Base.to_indices` among others", but after looking again at the source code of ModularIndices, it looks like that’s the only method needed! Though probably `Base.checkbounds` is good to define too, to say when the access is inbounds or not. It’s only a few lines of code so I think it should be pretty easy to define your own. InvertedIndices is a bit longer and more complicated because it’s doing a more complicated operation.

2 Likes

Unfortunately this needs a reference to `a` which isn’t always available. (In Python I can specify “next-to-last index” without needing that reference – just `ix = -2`.)

This seems like the right solution.

Maybe something like

``````struct LastBut{T}
n::T
end

getindex(a::AbstractArray, lb::LastBut) = a[lastindex(a) - lb.n]
``````

The package EndpointRanges.jl does exactly this. Plus methods to allow things like `iend-2` to adjust what’s stored. (Although maybe it was written before Base handled this? Certainly before `begin` worked.)

6 Likes

Yeah, exactly. `Base.to_indices` is just a way to intercept indexing at a different level to be able to make it work for e.g. any dimension of a multidimensional array.

I think it would be nice for Julia to move away from being “indexed from 1” toward “abstract” indexing: currently people usually use `a[3]` even when they mean more generally `a[begin+2]`. I wonder if there’s a way to encourage index-generic programming like this.

Abstract indexing works completely if you want to use it, doesn’t it?

For convenience it is still nice to use numbers, but in packages it would be nice if we stick to the generic way.

It is up to the coder at this point; I imagine that older code will be updated on demand (by someone making a PR when they need it), but pretty much all new code should the relevant generic constructs.