Equivalent of `last` for iterators

While responding to a different topic, I needed the last element of an iterator. This has at least one fairly simple and efficient solution which has appeared elsewhere:

itrlast(itr) = foldl((_,y)->y,itr)

but last can also return several elements from the end of a collection, so here is a suggested function to achieve this generally:

using CircularArrays
using IterTools

function itrlast(itr, n::Integer)
    n > 0 || error("Must keep at least one element")
    pi = peekiter(itr)
    e = peek(pi)
    isnothing(e) && "Iterator is empty"
    T = eltype(something(e))
    buf, i = foldl(((buf,i),e)->(buf[i+1] = e; (buf,i+1)),pi;
    i<n && error("Iterator has less than $n elements")
    @view buf[i-n+1:i]

With this method defined, the following works:

julia> itrlast(1:10,2)
2-element view(CircularVector(::Vector{Int64}), 9:10) with eltype Int64:

and also:

julia> itrlast(Iterators.take(iterated(x->√(6+x),1.0),23),5)
5-element view(CircularVector(::Vector{Float64}), 19:23) with eltype Float64:

This function, and perhaps last as well, can be generalized to “index” any iterator with an index range (e.g. 1:3:10 or ?? last(1:3:end, 5) of an itr ??).

Comments, ideas welcome.

1 Like

The most efficient thing is to first(Iterators.reverse(itr)). This works for any iterator that supports the reverse iteration interface (many but not all iterators). There was a PR to make this the default behavior for last (julia#42991), but it was reverted as being too breaking.

Of course, many iterators that support reverse iteration also implement last.

If there is any iterator that does not support reverse iteration but could (efficiently), a pull request is generally welcome. There are some open issue that are waiting for someone to tackle them, e.g. julia#39079 and julia#43740.

This is fine, but the reason that last doesn’t do this kind of thing in general is that it is documented as being O(1).