Stream: helpdesk (published)

Topic: Cannot `collect` a sequence of `Iterators`


view this post on Zulip Fons van der Plas (Jan 20 2025 at 10:33):

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.

view this post on Zulip Sundar R (Jan 20 2025 at 11:11):

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.

view this post on Zulip Sundar R (Jan 20 2025 at 11:12):

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?

view this post on Zulip Fons van der Plas (Jan 20 2025 at 11:13):

ah shame that they dont compose! thats unexpected... and also tricky that the error message is not helpful

view this post on Zulip Fons van der Plas (Jan 20 2025 at 11:14):

i want to drop from the end, but I can just use indexing begin:end-n :)

view this post on Zulip Mason Protter (Jan 20 2025 at 13:22):

I think this only really sensible in cases where length is defined, so @Sundar R's method seems better.

view this post on Zulip Mason Protter (Jan 20 2025 at 13:22):

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

view this post on Zulip Fons van der Plas (Jan 20 2025 at 13:24):

or we define keys(t::Take) = take(keys(t.iter), t.n)?

view this post on Zulip Mason Protter (Jan 20 2025 at 13:41):

Seems like it'd just turn into an infinite recursion

view this post on Zulip Mason Protter (Jan 20 2025 at 13:41):

without defining a bunch of base-cases

view this post on Zulip Mason Protter (Jan 20 2025 at 13:42):

I'm sure it's quite doable though

view this post on Zulip Mason Protter (Jan 20 2025 at 13:43):

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:

view this post on Zulip DrChainsaw (Jan 21 2025 at 07:41):

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