Python — Iterate an iterator twice

Question:

Edit: There is a similar question here that deals with iterator resetting. The accepted answer below however addresses the actual issue of nested iterators, and handles an easy to miss issue, whereby the nested iterator doesn’t reset.

Is there any way to iterate over an iterator twice in python?

In the example code below I can see that the second iteration is operating on the same object as the first, and thus yields a weird result. Contrast this with the C# below that yields the result I’m after.

Is there any way to do what I want. I was wondering if I could make a copy of the iterator or “retrieve” the function it came from, but maybe there’s a simpler way. (I know I could just call MyIter() twice in the toy example below, but that’s useless if I don’t know where the iterator came from and isn’t what I’m after!).

def MyIter():
  yield 1;
  yield 2;
  yield 3;
  yield 4;

def PrintCombos(x):
  for a in x:
      for b in x:
          print(a,"-",b);

PrintCombos(MyIter());

gives

1 - 2
1 - 3
1 - 4

Contrast with:

static IEnumerable MyIter()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
}

static void PrintCombos(IEnumerable x)
{
    foreach (var a in x)
        foreach (var b in x)
            Console.WriteLine(a + "-" + b);
}

public static void Main(String[] args)
{
    PrintCombos(MyIter());
}

Which gives:

1-1
1-2
1-3
1-4
2-1
2-2
. . .
Asked By: c z

||

Answers:

You could use itertools.tee to create multiple copies of the generator

from itertools import tee

def MyIter():
    yield 1
    yield 2
    yield 3
    yield 4

def PrintCombos(x):
    it1, it2 = tee(x, 2)
    for a in it1:
        it2, it3 = tee(it2, 2)
        for b in it3:
        print("{0}-{1}".format(a, b))

PrintCombos(MyIter())
Answered By: Chris Mueller

I find using list comprehension for this type of problem is most effective at getting your desired result.

x = [1,2,3,4]
y = [1,2,3,4]

spam = [[s,t] for s in x for t in y]

for x in spam:
    print('%s - %s' %(x[0], x[1]))

output:

1 - 1
1 - 2
1 - 3
1 - 4
2 - 1
2 - 2
2 - 3
2 - 4
3 - 1
3 - 2
3 - 3
3 - 4
4 - 1
4 - 2
4 - 3
4 - 4
Answered By: Feivel

itertools.tee creates independent iterators from a single iterable. However, once new iterables are creates, the original iterable should not be used anymore.

import itertools
def MyIter():
    yield 1;
    yield 2;
    yield 3;
    yield 4;

def PrintCombos(x):
    xx = []
    xx.append(itertools.tee(x))
    n = 0
    for a in xx[0][0]:
        xx.append(itertools.tee(xx[n][1]))
        for b in xx[n+1][0]:
            print('%s - %s' % (a,b));
        n += 1

PrintCombos(MyIter());
Answered By: Andrej Debenjak

If you build your own, you can reset it.

class MyIterable():
    def __init__(self, account, list):
        self.list = list

    def __iter__(self):
        self.next_counter = 0 # magic, we have a reusable iterator!
        return self

    def __next__(self):
        if self.next_counter < len(self.list):
            result = self.list[self.next_counter]
            self.next_counter += 1
            return result

        raise StopIteration

Curious to know if the community thinks this is a good solution

Answered By: KCD