-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Iterator slicing/indexing #26
Comments
Great idea and thanks for the jumpstart! I'd love a PR. I think it might be good to enforce the restriction that indices be strictly increasing. Perhaps just error if
Technically Don't forget
Another thought; this, |
Thanks for the feedback!
I don't think it's possible to do that here. That was actually my first try, but it didn't work: struct Slice{I, S}
iter::I
slice::S
end
const slice = Slice
function Base.start(it::Slice)
return (0, 0, start(it.iter), start(it.slice))
end
Base.done(it::Slice, ::Void) = true
Base.done(it::Slice, state) = done(it.slice, state[4]) ||
done(it.iter, state[3])
function Base.next(it::Slice, state)
local x
n, k_prev, si, ss = state
k, ss = next(it.slice, ss)
if k <= k_prev
if k_prev < 1
error("Slicing index starts with a number smaller than 1.")
end
error("Slicing index is not strictly increasing ($k_prev -> $k).")
end
# @assert n < k
while n < k
if done(it.iter, si)
# return (????, (n, k, si, ss))
return nothing # cannot return any useful result here
end
x, si = next(it.iter, si)
n += 1
end
return (x, (n, k, si, ss))
end
Base.iteratorsize(::Type{<: Slice}) = Base.SizeUnknown()
using Base.Test
@show collect(slice(1:100, 1:2:5))
@show collect(1:2:5)
@test collect(slice(1:100, 1:2:5)) == collect(1:2:5)
@test collect(slice(1:5, 1:2:10)) == collect(1:2:5)
# This doesn't work:
@test collect(slice(1:10, [1, 3, 7, 11])) == [1, 3, 7, 11] The problem is that
I agree that a cheap
Hopefully, we can get rid of this sub-optimal solution in Julia 1.0: JuliaLang/julia#18823 But until Julia 1.0, this is all we can do, I think. Is it OK to advance iterators in
Ah, good catch. Thank. I'll fix that when I'm sending a RP. More brainstorming for the name:
I like From your suggestions, I think
Yeah I agree. I think we can dispatch on the type of the second argument (indices or an index) but I think we can only combine two, like this: Combining "slice" and f1(iter, indices) # == "slice"(iter, indices)
f1(iter, index::Integer) # == nth(iter, index) Combining "slice" and f2(iter, indices) # == "slice"(iter, indices)
f2(iter, stride::Integer) # == takenth(iter, stride) I think choosing one of the two cases narrows possible set of names. For example, the name If what you mean by "combine them" is just reusing the implementation, we can do it after a more efficient specific implementation for |
Come to think of it, I just realize I don't understand why for (i, x) in enumerate(iter)
if i % 2 == 1
f(iter, x)
end
end and for x in takenth(iter, 2)
f(iter, x)
end are not equivalent if using Base.Test
using IterTools
buf = IOBuffer(join(1:10, "\n"))
for (i, line) in enumerate(takenth(eachline(buf), 2))
@show i, line
if i > 2
break
end
end
@test readline(buf) == "7" It is common for stateful algorithm to provide an iterator interface (e.g., DifferentialEquations.jl) and it would be nice if IterTools.jl can interact well with such algorithms. For this, IterTools has to have a strong guarantee that it advances underlying iterator only at the very last moment. It means to advance iterators in Do you think this stronger guarantee on how IterTools advances underlying iterators makes sense? Do you think the |
So I implemented iteratorsize/iteratoreltype/eltype and also length for the case indices are from OrdinalRange: I can send a PR with this code (with some addition of docstring) if next-in-done hack is OK and the name is decided. |
Can you come up with an example you're interested in where your implementation is faster than this one: function myviewing(itr, inds)
imap(last, Iterators.filter((x)->first(x) in inds, enumerate(itr)))
end It's faster in most cases. For very large |
My implementation is doing mutation on fields of Any type. It's certainly not aimed for performance, at least at the moment. It's more about implementing it correctly. But I think faster implementation is possible in Julia 1.0. The |
Also, using Base.Test
using IterTools
function myviewing(itr, inds)
imap(last, Iterators.filter((x)->first(x) in inds, enumerate(itr)))
end
@testset "myviewing" begin
buf = IOBuffer(join(1:10, "\n"))
inds = [1, 3, 5]
for (i, line) in zip(inds, myviewing(eachline(buf), inds))
@test string(i) == line
end
@test_broken readline(buf) == "6"
end But this is how |
Is there a way to slice/index arbitrary iterator in Julia? Can you add a function ("
slice
") that can do that? It's like Python'sitertools.islice
and it doesbut it also works with infinite iterator and indices, provided that indices are strictly increasing (otherwise you need to cache the whole history). I actually coded up a quick implementation:
Side notes: Besides obvious lack of documentation and good error message (rather than
@assert
), the most smelly part of the code is the mutableSliceState
and the calls to thenext
of the underlying iterators. But it seems that this is the usual way to solve this kind of problem for Julia <= 0.6. I guess it's going to be easier with the new iteration protocol for Julia 1.0.Can IterTools.jl and/or Base already do it? Am I missing something? If not, do you think it is a good API to add in IterTools.jl? I can make a PR with a bit more refined code if you like.
The text was updated successfully, but these errors were encountered: