# Iterators and Generators: exam-level questions

If you need help reviewing Iterators and Generators, take a look at these resources:

Each question has a "Toggle Solution" button -- click it to reveal that question's solution.

## Iterators: Cross out the errors

### Question 1

Cross out any incorrect or unnecessary lines. You should not need to write any new lines to make the code work. Do not cross out any doctests.

``````class Naturals:
"""Doctests.

>>> n = Naturals()
>>> i = iter(n)
>>> next(i)
0
>>> next(i)
1
>>> next(i)
2
"""
def __init__(self):
self.cur = 0

def __iter__(self):
def __iter__(self, start):
self.cur = start
while True:
self.cur += 1
return self.cur
return NatIter(self.cur)

def __next__(self):
tmp = self.cur
self.cur += 1
return tmp

class NatIter(Iterator)
class NatIter:
def __init__(self):
def __init__(self, start):
self.cur = start

def __iter__(Self):
return self

def __next__(self):
tmp = self.cur
self.cur += 1
return tmp``````
``````class Naturals:
"""Doctests.

>>> n = Naturals()
>>> i = iter(n)
>>> next(i)
0
>>> next(i)
1
>>> next(i)
2
"""
def __init__(self):
self.cur = 0

def __iter__(self):
return NatIter(self.cur)

class NatIter:
def __init__(self, start):
self.cur = start

def __iter__(Self):
return self

def __next__(self):
tmp = self.cur
self.cur += 1
return tmp``````

## Iterators: Fill in the blanks

### Question 2

Fill in the implementation of the iterator for the Rlist class.

``````class Rlist:
"""Doctests

>>> r = Rlist(1, Rlist(2, Rlist(3, Rlist(4))))
>>> for item in r:
...     print(item)
1
2
3
4
"""
class EmptyList:
pass

empty = EmptyList()

def __init__(self, first, rest=empty):
self.first = first
self.rest = rest
self.cur = self

def __iter__(self):
return ______

def __next__(self):
if self.cur == ______:
raise ______
else:
result = ______
______ = self.cur.rest
return result``````
``````class Rlist:
class EmptyList:
pass

empty = EmptyList()

def __init__(self, first, rest=empty):
self.first = first
self.rest = rest
self.cur = self

def __iter__(self):
return self

def __next__(self):
if self.cur == Rlist.empty:
raise StopIteration
else:
result = self.curr.first
self.cur = self.cur.rest
return result``````

Since we are writing a `__next__` method for the `Rlist` class, the `Rlist` class is technically an iterator. As such, its `__iter__` method can just return `self`. In the `__next__` method, if the current Rlist is empty, we must raise a `StopIteration` exception. Otherwise, we will return the element (`self.curr.first`) at the current node, and change our point (`self.curr`) to the next node in the Rlist (`self.cur.rest`)."""

## Generators: Code-writing questions

### Question 3

Write a generator function `zip` that takes two iterators and yields elements of thsoe iterators in pairs (see the doctests for clarification). `zip` will stop once one of the input iterators stops.

``````def zip(iter1, iter2):
"""Doctests

>>> i1 = iter([1, 2, 3, 4])
>>> i2 = iter([5, 6, 7])
>>> gen = zip(i1, i2)
>>> for elem in gen:
...     print(elem)
(1, 5)
(2, 6)
(3, 7)
"""
"*** YOUR CODE HERE ***"``````
``````def zip(iter1, iter2):
while True:
try:
yield (next(iter1), next(iter2))
except StopIteration:
break``````

## Generators: Fill in the blank

### Question 4

Fill in the implementation of `pascals`, a generator function that yields successive lines of Pascal's triangle every time `next` is called. Each line should be represented as a Python list.

Hint: a description of Pascal's triangle can be found here

``````def pascals():
"""Doctests

>>> p = pascals()
>>> next(p)

>>> next(p)
[1, 1]
>>> next(p)
[1, 2, 1]
>>> next(p)
[1, 3, 3, 1]
>>> next(p)
[1, 4, 6, 4, 1]
"""
curr = ______
while True:
yield curr
i, new = 1, 
while ______:
new.append(______ + ______)
i += 1
new.append(1)
curr = new``````
``````def pascals():
curr = 
while True:
yield curr
i, new = 1, 
while i < len(curr):
new.append(curr[i-1] + curr[i])
i += 1
new.append(1)
curr = new``````