Homework 10 Solutions

Solution Files

You can find the solutions in hw10.py.

Required Questions

Iterators and Generators

Q1: Restart

Use Ok to test your knowledge with the following What would Python display questions:

python3 ok -q restart -u
>>> class IteratorA:
...    def __init__(self):
...        self.start = 10
...    def __next__(self):
...        if self.start > 100:
...            raise StopIteration
...        self.start += 20
...        return self.start
...    def __iter__(self):
...        return self
>>> iterator = IteratorA()
>>> [num for num in iterator]
[30, 50, 70, 90, 110]
>>> [num for num in iterator]
[]
>>> class IteratorB:
...    def __init__(self):
...        self.start = -6
...    def __next__(self):
...        if self.start > 10:
...            raise StopIteration
...        self.start += 3
...        return self.start
...    def __iter__(self):
...        return self
>>> iterator = IteratorB()
>>> [num for num in iterator]
[-3, 0, 3, 6, 9, 12]
>>> [num for num in iterator]
[]

The outputs of the list comprehensions are like that because the instance variables are not reset each time a for loop is started. Therefore, when a StopIteration exception is raised at the end of the first list comprehension, it will be raised immediately at the beginning of the second. With that in mind, try writing an iterator that "restarts" every time it is run through a for loop.

class IteratorRestart:
    """
    >>> iterator = IteratorRestart(2, 7)
    >>> for num in iterator:
    ...     print(num)
    2
    3
    4
    5
    6
    7
    >>> for num in iterator:
    ...     print(num)
    2
    3
    4
    5
    6
    7
    """
    def __init__(self, start, end):
self.start = start self.end = end self.current = start
def __next__(self):
if self.current > self.end: raise StopIteration self.current += 1 return self.current - 1
def __iter__(self):
self.current = self.start return self

Use Ok to test your code:

python3 ok -q IteratorRestart

Q2: Amplify

Implement amplify, a generator function that takes a one-argument function f and a starting value x. The element at index k that it yields (starting at 0) is the result of applying f k times to x. It terminates whenever the next value it would yield is a falsy value, such as 0, "", [], False, etc.

def amplify(f, x):
    """Yield the values x, f(x), f(f(x)), ... that are all truthy values
    and stop yielding once the sequence reaches the first falsy value.

    >>> gen1 = amplify(lambda s: s[1:], 'boxes')
    >>> [next(gen1) for _ in range(5)]
    ['boxes', 'oxes', 'xes', 'es', 's']
    >>> try:
    ...     next(gen1)
    ... except StopIteration:
    ...     print('Correctly raised StopIteration')
    ... else:
    ...     print('Expected StopIteration error but was not raised')
    Correctly raised StopIteration
    >>> gen2 = amplify(lambda x: x // 2 - 1, 14)
    >>> [next(gen2) for _ in range(3)]
    [14, 6, 2]
    >>> try:
    ...     next(gen2)
    ... except StopIteration:
    ...     print('Correctly raised StopIteration')
    ... else:
    ...     print('Expected StopIteration error but was not raised')
    Correctly raised StopIteration
    """
while x: yield x x = f(x)

Use Ok to test your code:

python3 ok -q amplify

Q3: Countdown

Write both a generator function and an iterator (that is not a generator) that count down to 0.

def countdown(n):
    """
    A generator that counts down from N to 0.
    >>> for number in countdown(5):
    ...     print(number)
    ...
    5
    4
    3
    2
    1
    0
    >>> for number in countdown(2):
    ...     print(number)
    ...
    2
    1
    0
    """
while n >= 0: yield n n = n - 1
class Countdown:
    """
    An iterator that counts down from N to 0.
    >>> for number in Countdown(5):
    ...     print(number)
    ...
    5
    4
    3
    2
    1
    0
    >>> for number in Countdown(2):
    ...     print(number)
    ...
    2
    1
    0
    """
    def __init__(self, cur):
        self.cur = cur

    def __next__(self):
if self.cur < 0: raise StopIteration self.cur -= 1 return self.cur + 1
def __iter__(self): """So that we can use this iterator as an iterable.""" return self

Use Ok to test your code:

python3 ok -q countdown
python3 ok -q Countdown

Walkthrough:

YouTube link

Submit Assignment

Submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.