I need to walk two custom iterators and compare elements. Do something like zip exists for iterators? If I need to implement my own, what is the argument type for my zip-iterator?
iterator zip[T](a:?[T],b:?[T]):(T,T)
I tried this, too. I could not find a viable solution. You can do this with closure iterators quite easily. But the problem remains that the majority of iterators in the standartd library are inline iterators. So you would need some mechanism that would automatically lift an inline iterator to a closure iterator.
Thanks for the links! Closure iterators gets the job done for me, but I agree It would be nice if we could automatically "lift" inline iterators so they can be composed.
Converting to Seq[T] will work but negates the reason for choosing iterators in the first place.
iterator A(): int =
var i = 0
while i < 10:
yield i
i += 1
proc B(): iterator(): int =
return iterator(): int =
var i = 0
while i < 10:
yield i
i += 2
proc zip[T](a:iterator():T,b:iterator():T):iterator():(T,T) =
return iterator():(T,T) =
let ait = a
let bit = b
while true:
let ares = ait()
let bres = bit()
if finished(ait) or finished(bit):
break
yield (ares,bres)
let it = zip(B(),B()) # works
# let it = zip(A,B()) # Can we lift A somehow to make this work?
for e in it():
echo "Res ", e
I got this:
import macros
iterator A(): int =
var i = 0
while i < 10:
yield i
i += 1
macro iterType(arg: typed): untyped =
# only this example case is covored
# fails on call and dotExpr
let impl = arg.symbol.getImpl
impl.expectKind nnkIteratorDef
result = impl[3][0]
proc AA(): iterator(): iterType(A) =
return iterator(): iterType(A) =
for it in A():
yield it
template AAA(): untyped =
var iter: iterator(): iterType(A) =
iterator(): iterType(A) =
for it in A():
yield it
iter
template lift(arg: typed): untyped =
var iter = iterator(): iterType(arg) =
for it in arg():
yield it
iter
proc B(): iterator(): int =
return iterator(): int =
var i = 0
while i < 10:
yield i
i += 2
proc zip[T](a:iterator():T,b:iterator():T):iterator():(T,T) =
return iterator():(T,T) =
let ait = a
let bit = b
while true:
let ares = ait()
let bres = bit()
if finished(ait) or finished(bit):
break
yield (ares,bres)
let it = zip(B(),B()) # works
let it2 = zip(AA(),B()) # Can we lift A somehow to make this work?
let it3 = zip(AAA(), B())
let it4 = zip(lift(A), B())
for e in it():
echo "Res it ", e
for e in it2():
echo "Res it2 ", e
for e in it3():
echo "Res it3 ", e
for e in it4():
echo "Res it4 ", e
I let a few version in that I used in between to get to my result.
For a general library-like facility, one would probably want to be able to zip iterators returning different types in general. In the language of Krux02's solution:
proc zip[T1, T2](a: iterator():T1, b: iterator():T2): iterator():(T1,T2) =
return iterator(): (T1, T2) =
Also, one would probably want to generalize the above to be able to zip any number of iterators, not only two. That would probably require some more complex macro manipulations.
Finally, there is an unmentioned so far choice in how to deal with iterator exhaustion - breaking when all iterators are finished or breaking when the first is finished. Nim has convenient default values for types which could be used after an iterator is exhausted. Which is a better choice there almost surely can only be decided by the invoker of the zip().
I say all this in case there is an ambitious library developer out there. :-)