Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate over all but the last index of an AbstractArray

In Julia, the recommended way to iterate over all indices of an AbstractArray is to use eachindex, e.g.,

for i in eachindex(a)
    do_something(a[i], i)
end

In contrast to 1:length(a), eachindex(a) supports arrays with unconventional indexing, i.e., indices not starting at 1. Additionally, it is more efficient for arrays with slow linear indexing.

If I want to skip the first index I can use Iterators.drop(eachindex(a), 1) (is there a better way?) but how do I skip the last one in a generic way?

like image 296
tim Avatar asked Apr 26 '17 17:04

tim


3 Answers

A "front" iterator is relatively simple and generally useful. Edit: it's also totally overkill to define it in full generality just for this case. It's much easier and simpler to rely on Base's builtins with a definition like:

front(itr, n=1) = Iterators.take(itr, length(itr)-n)

This will work for all iterators with length defined — which will include everything that eachindex will return.


Alternatively, you can define a specialized iterator from first principles that doesn't depend upon length being defined. I'm not aware of such a structure in any existing packages. Using Julia 0.6, an implementation could look like:

struct Front{T}
    itr::T
end
# Basic iterator definition
function Base.start(f::Front)
    s = start(f.itr)
    done(f.itr, s) && throw(ArgumentError("cannot take the front of an empty iterator"))
    return next(f.itr, s)
end
function Base.next(f::Front, state)
    val, s = state
    return val, next(f.itr, s)
end
Base.done(f::Front, state) = done(f.itr, state[2])

# Inherit traits as appropriate
Base.iteratorsize(::Type{Front{T}}) where {T} = _dropshape(Base.iteratorsize(T))
_dropshape(x) = x
_dropshape(::Base.HasShape) = Base.HasLength()
Base.iteratoreltype(::Type{Front{T}}) where {T} = Base.iteratoreltype(T)
Base.length(f::Front) = length(f.itr) - 1
Base.eltype(f::Front{T}) where {T} = eltype(T)

Now:

julia> collect(Front(eachindex(rand(5))))
4-element Array{Int64,1}:
 1
 2
 3
 4

julia> collect(Front(eachindex(sprand(3, 2, .2))))
5-element Array{CartesianIndex{2},1}:
 CartesianIndex{2}((1, 1))
 CartesianIndex{2}((2, 1))
 CartesianIndex{2}((3, 1))
 CartesianIndex{2}((1, 2))
 CartesianIndex{2}((2, 2))
like image 168
mbauman Avatar answered Nov 03 '22 01:11

mbauman


Another way to define @MattB.'s Front is

front(itr,n=1) = (first(x) for x in Iterators.partition(itr,n+1,1))

This also gives:

julia> front(eachindex([1,2,3,4,5]))|>collect
4-element Array{Int64,1}:
 1
 2
 3
 4

and as a bonus:

julia> front(eachindex([1,2,3,4,5]),2)|>collect
3-element Array{Int64,1}:
 1
 2
 3

the corresponding iterator to drop(eachindex([1,2,3,4,5]),2).

like image 25
Dan Getz Avatar answered Nov 02 '22 23:11

Dan Getz


There's also the following:

for I in CartesianRange(Base.front(indices(A))) @show I A[I, :] end

On A = reshape(1:27, 3, 3, 3) this yields

I = CartesianIndex{2}((1,1)) A[I,:] = [1,10,19] I = CartesianIndex{2}((2,1)) A[I,:] = [2,11,20] I = CartesianIndex{2}((3,1)) A[I,:] = [3,12,21] I = CartesianIndex{2}((1,2)) A[I,:] = [4,13,22] I = CartesianIndex{2}((2,2)) A[I,:] = [5,14,23] I = CartesianIndex{2}((3,2)) A[I,:] = [6,15,24] I = CartesianIndex{2}((1,3)) A[I,:] = [7,16,25] I = CartesianIndex{2}((2,3)) A[I,:] = [8,17,26] I = CartesianIndex{2}((3,3)) A[I,:] = [9,18,27]

like image 25
tholy Avatar answered Nov 03 '22 01:11

tholy