Python -- Iterate an iterator twice [duplicate]
Asked Answered
C

4

7

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
. . .
Cinder answered 2/12, 2016 at 15:7 Comment(2)
You can iterate over iterators unlimited amount of times in python. However if you are refering to generators (which you are by the looks of code given) there is no way of doing that multiple times on generator itself. You can however save generator results in memory and iterate over that. In the example given by you you'd do that by calling PrintCombos(list(MyIter()))Hokusai
itertools.tee might be what you're looking for.Gules
T
4

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())
Titanism answered 2/12, 2016 at 15:19 Comment(0)
I
1

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());
Illusionary answered 2/12, 2016 at 15:40 Comment(0)
C
0

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

Cystoscope answered 14/9, 2022 at 11:15 Comment(0)
H
-1

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
Hooey answered 2/12, 2016 at 15:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.