Hey! I tried to be clever and I wrote this function, which drops the last n
elements from an iterator:
droplast(iter, n) = Iterators.reverse(Iterators.drop(Iterators.reverse(iter), n))
But when I try it on a simple vector:
droplast([1,2,3,4],2) |> collect
I get an error! I don't understand the error.
Q: what does the error mean, and how do I get the desired result?
ERROR: MethodError: no method matching keys(::Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}})
The function `keys` exists, but no method is defined for this combination of argument types.
Closest candidates are:
keys(::Base.TermInfo)
@ Base terminfo.jl:232
keys(::Cmd)
@ Base process.jl:716
keys(::Core.SimpleVector)
@ Base essentials.jl:944
...
Stacktrace:
[1] eachindex(itrs::Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}})
@ Base ./abstractarray.jl:318
[2] iterate(A::Base.Iterators.Reverse{Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}}})
@ Base.Iterators ./iterators.jl:143
[3] copyto!(dest::Vector{Int64}, src::Base.Iterators.Reverse{Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}}})
@ Base ./abstractarray.jl:934
[4] _collect(cont::UnitRange{…}, itr::Base.Iterators.Reverse{…}, ::Base.HasEltype, isz::Base.HasLength)
@ Base ./array.jl:722
[5] collect(itr::Base.Iterators.Reverse{Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}}})
@ Base ./array.jl:716
[6] |>(x::Base.Iterators.Reverse{Base.Iterators.Drop{Base.Iterators.Reverse{Vector{Int64}}}}, f::typeof(collect))
@ Base ./operators.jl:926
[7] top-level scope
@ REPL[4]:1
Some type information was truncated. Use `show(err)` to see complete types.
I remember seeing complaints recently (can't remember if it was on Slack or on Github) that our iterators don't compose, you often can't take the output of one Iterators
function and feed it to another and have it work. This seems to be an instance of that, Iterators.reverse
seems to expect that its argument will have a keys
method defined on it to get its indices, which drop
's return object Iterators.Drop
apparently doesn't provide. I'll link the Github discussion if I find it in my history.
In this case, if the iterator has a length (which should be the most common case),
droplast(iter, n) = Iterators.take(iter, length(iter) - n)
does what you want, right?
ah shame that they dont compose! thats unexpected... and also tricky that the error message is not helpful
i want to drop from the end, but I can just use indexing begin:end-n
:)
I think this only really sensible in cases where length is defined, so @Sundar R's method seems better.
We could probably make reverse(::Take{Reverse})
work, but it'd have to be done quite carefully, and it'd just be a game of whack-a-mole
or we define keys(t::Take) = take(keys(t.iter), t.n)
?
Seems like it'd just turn into an infinite recursion
without defining a bunch of base-cases
I'm sure it's quite doable though
julia> Base.keys(t::Iterators.Drop) = Iterators.drop(keys(t.xs), t.n);
julia> droplast(iter, n) = Iterators.reverse(Iterators.drop(Iterators.reverse(iter), n));
julia> sum(droplast(1:10, 2))
ERROR: StackOverflowError:^C
SYSTEM (REPL): showing an error caused an error
ERROR: InterruptException:
I guess you could roll your own iterator struct which queues n
elements internally and returns the top if the wrapped iterator does not return nothing .
Shouldn't be too bad performance wise if you take the extra effort to make it a ringbuffer.
Last updated: Jan 29 2025 at 04:38 UTC