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
. . .
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())
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
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());
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
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
. . .
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())
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
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());
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