Homework 10: Iterators and Generators

Due by 11:59pm on Wednesday, April 22

Instructions

Download hw10.zip. Inside the archive, you will find a file called hw10.py, along with a copy of the ok autograder.

Submission: When you are done, submit the assignment to Gradescope. You may submit more than once before the deadline; only the final submission will be scored. Check that you have successfully submitted your code on Gradescope. See Lab 0 for more instructions on submitting assignments.

Using Ok: If you have any questions about using Ok, please refer to this guide.

Readings: You might find the following references useful:

Grading: Homework is graded based on correctness. Each incorrect problem will decrease the total score by one point. This homework is out of 6 points.

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):
        "*** YOUR CODE HERE ***"

    def __next__(self):
        "*** YOUR CODE HERE ***"

    def __iter__(self):
        "*** YOUR CODE HERE ***"

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
    """
    "*** YOUR CODE HERE ***"

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
    """
    "*** YOUR CODE HERE ***"
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):
        "*** YOUR CODE HERE ***"

    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

Submit Assignment

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