# Array step index

There are arrays whose first index isn’t 1. If I want the next value after the first index, is it always `x[begin+1]`, or is there a generalization like `x[begin+step]`?

For `AbstractArray`, almost all generic code is written assuming that the array step is 1. In theory, there’s nothing stopping someone from making an array with a different step, but in practice it might as well not be an `<:AbstractArray` because it won’t work with anything.

However, for `String`s, you often have a situation where the indices are non-uniform and have weird steps, so there’s a `nextind` function that does what you want.

``````julia> for x in ("αβγ", (:α, :β, :γ), [:α, :β, :γ])
@show x
@show nextind(x, firstindex(x))
@show x[nextind(x, begin)]
println()
end
x = "αβγ"
nextind(x, firstindex(x)) = 3
x[nextind(x, begin)] = 'β'

x = (:α, :β, :γ)
nextind(x, firstindex(x)) = 2
x[nextind(x, begin)] = :β

x = [:α, :β, :γ]
nextind(x, firstindex(x)) = 2
x[nextind(x, begin)] = :β
``````
1 Like

I think, that, unfortunately, the only safe way to iterate over non-unitary steps are:

1. Using `keys` to have access to the whole list of indexes.
2. Calling `iterate` by hand, which does not actually gives you the indexes, but allows you to go step-by-step.

I disagree a little with Mason, even if he is probably more informed than me, because I think there are a lot of algorithms that just iterate over `AbstractArray` or use other functions never relying directly on incrementing steps one by one, but you will probably hit one or another function that makes this assumption.

What’s wrong with `nextind` (assuming it’s been implemented for the collection in question)?

Yeah, this depends a lot on what functions you’re using. There are definitely lots of functions which will be okay, but I think using an `AbstractArray` with non-unit steps is just generally going to be a minefield when faced with generic function implementations.

The worst part is that you won’t even get method-errors when you end up hitting a function that assumes unit steps, instead you’ll just get a silently incorrect program, and a lot of `AbstractArray` code in `Base` and `LinearAlgebra` is somewhat deeply nested, so making sure that a function makes correct assumptions about step sizes will involve a large amount of detective-work, looking at nested call chains and each function in that chain to make sure it doesn’t index by steps of 1. But even if `Base` and `LinearAlgebra` were safe in this regard, many many packages won’t be.

For what I understand, `nextind` is not part of any `AbstractArray` or general collection interfaces but a function to be used with `String`s. In other words, `nextind` is not a generic function that obtains the next index but by chance was only implemented for `String`, `nextind` is a `Base` function without any meaning outside of the scope of the `String` type. Re-using the name seems to be an inadequate decision, it is punning.

I agree. In my package’s code I tried to not suppose the step size, but in some cases it is hard to even not assume one-based indexing.

Hm, we’ll someone definitely took the time to implement `nextind` for abstract arrays and tuples:

``````julia> methods(nextind)
# 9 methods for generic function "nextind":
[1] nextind(s::SubString{String}, i::Int64) in Base at strings/substring.jl:96
[2] nextind(s::String, i::Int64) in Base at strings/string.jl:137
[3] nextind(t::Tuple, i::Integer) in Base at tuple.jl:67
[4] nextind(::AbstractArray, i::Integer) in Base at abstractarray.jl:150
[5] nextind(s::AbstractString, i::Int64) in Base at strings/basic.jl:537
[6] nextind(s::AbstractString, i::Int64, n::Int64) in Base at strings/basic.jl:539
[7] nextind(s::AbstractString, i::Integer) in Base at strings/basic.jl:536
[8] nextind(s::AbstractString, i::Integer, n::Integer) in Base at strings/basic.jl:535
[9] nextind(a::AbstractArray{var"#s828",N} where var"#s828", i::CartesianIndex{N}) where N in Base.IteratorsMD at multidimensional.jl:149
``````

Though only the `String` method is actually documented. Perhaps the other methods could be documented as well, and we could make it part of the `AbstractArray` interface.

Nice, I did not know someone had implemented this. However, if it is not in the `Base` documentation then it is not part of the public API. I would not suggest its use, for now it is just an implementation detail. I can open a PR tomorrow to check if this is intended to be public API.

According to Interfaces · The Julia Language the axes of an array are always `AbstractUnitRange{<:Integer}`, so yes the step is always 1.

4 Likes

If you want a non-unit step, just use views to re-index into your contiguous (dense) 1-step array with a `StepRange` like `@view v[begin:2:end]` or `@view A[begin:2:end, :]`. The resulting object will have ~half as many values and will still act like a normal 1-step array with an axis like `1:length(v)÷2`… but will seamlessly interface with BLAS as a strided array with non-unit steps.

In other words, while Julia’s arrays always expose 1-step (unit range) axes, their storage can be whatever it needs to be — and views can effortlessly and efficiently do this transformation.

4 Likes