how to iterate with two iterators

Viewed 330

I would like to use nimbioseq and iterate two files with the same number of sequences (using the readSeq()iterator), as:

for seq1, seq2 in readSeq(file1), readSeq(file2):
  echo seq1.id, "\t", seq2.id

For this scenario I suppose I need some sort of "zipping" operator, which I couldn't understand how to use [ found this: https://hookrace.net/nim-iterutils/iterutils.html#zip.i,,Iterable[S] ].

or alternatively understand how to get a single "iteration" outside a for loop (if possible):

for seq1 in readSeq(file1):
  let seq2 = readSeq(file2);
  echo seq1.id, "\t", seq2.id

Thanks for you help!

3 Answers

toClosure from iterutils is limited, but you can:

import iterutils

template initClosure(id,iter:untyped) =
  let id = iterator():auto{.closure.} =
    for x in iter:
      yield x

initClosure(f1,readSeq(file1))
#creates a new closure iterator, 'f1'

initClosure(f2,readSeq(file2))
#creates a new closure iterator, 'f2'

for seq1,seq2 in zip(f1,f2):
  echo seq1.id,"\t",seq2.id

Edit: thanks to @pietropeter for pointing out the bug, here's their example rewritten using this template:

import iterutils
template initClosure(id:untyped,iter:untyped) =
  let id = iterator():auto {.closure.} =
    for x in iter:
      yield x

iterator letters: auto =
  for c in 'a' .. 'z':
    yield c

# Now requires a parameter
iterator numbers(s: int): int =
  var n = s
  while true:
    yield n
    inc n

initClosure(cletter,letters())
initClosure(numbers8,numbers(8))

for (c, n) in zip(cletter, numbers8):
  echo c, n

I'm going to use this iterators code from Manual, and insert your problem in it. I'm sure it has room for improvement:

type
  Task = iterator (r: var int)

iterator f1(r: var int){.closure.} =
  for n in [1, 3, 5]:
    r = n
    yield

iterator f2(r: var int){.closure.} =
  for n in [2, 4, 6]:
    r = n
    yield

proc runTasks(t: varargs[Task]) =
  var ticker = 0
  var r: int

  while true:
    var x = t[ticker mod t.len]
    x(r)
    echo r
    if finished(x): break
    inc ticker

runTasks(f1, f2)

You'll see in the output 1,2,3,4,5,6,6 (finished is prone to error, as stated in the manual, and returns the last item twice). You have to update the code, replacing r: var int with whatever type returns readSeq(file) (r: var Record, I think), and replace the iterators for n in [1, 2, 3] with for s in readSeq(file).

If the type of behaviour you want is that of zip, the one from iterutils seems to work fine. The only caveat is that it requires closure iterators (see manual for the difference between inline and closure iterators). Example (https://play.nim-lang.org/#ix=2yXV):

import iterutils

iterator letters: char {.closure.} =
  for c in 'a' .. 'z':
    yield c

iterator numbers: int {.closure.}=
  var n = 1
  while true:
    yield n
    inc n

for (c, n) in zip(letters, numbers):
  echo c, n

I see that readseq in nimbioseq is not closure but probably something like this could work (edit: its should not, see below):

iterator closureReadSeqs(filename: string): Record {.closure.} =
  for rec in readSeqs(filename):
    yield rec

Edit

For the case of iterator with a parameter in the comments, the fix is to have a proc that returns an iterator (which will be a closure iterator by default in this case). Updated example (https://play.nim-lang.org/#ix=2z0e):

import iterutils

iterator letters: char {.closure.} =
  for c in 'a' .. 'z':
    yield c

# Now requires a parameter
proc numbers(s: int): iterator(): int =
  return iterator(): int =
    var n = s
    while true:
      yield n
      inc n

let numbers8 = numbers(8)
for (c, n) in zip(letters, numbers8):
  echo c, n

Now my best guess on how to make this work for nimbioseq is:

proc closureReadSeqs(filename: string): iterator(): Record =
  return iterator(): Record =
    for rec in readSeqs(filename):
      yield rec
Related