Home CS61A: Generators
Post
Cancel

CS61A: Generators

Generators

  • A special form of an iterator that is returned from a generator function. The generator object iterates over all yielded values of a function.
    • In the generator funtion, we use the yield keyword to return subsequent values.
    • We may peruse through the generator using similar techniques to the iterator, such as next()
  • Calling if a return statement is called, the generator would pre-emptively stop yielding new values.
  • Ex:
1
2
3
4
5
6
7
8
9
def plus_minus(x):
    yield x
    yield -x
    return 1

plus_minus_4 = plus_minus(4)
print(type(plus_minus_4))
print(next(plus_minus_4))
print(next(plus_minus_4))
1
2
3
<class 'generator'>
4
-4
  • A generator function is a function that yields values instead of returning them
  • A normal function returns a value just once; a generator function can yield multiple times
  • A generator is an iterator crated automatically by calling a generator function
  • When a generator function is called, it returns a generator that iterates over its yields
1
2
3
4
5
6
7
8
9
10
11
12
def evens(start, end):
    even = start + (start % 2)
    while even < end:
        yield even
        even += 2

t = evens(2, 10)
print(type(t))
print(next(t))
print(next(t))
print(next(t))
print(next(t))
1
2
3
4
5
6
7
8
9
10
11
<class 'generator'>
2
4
6
8





range
  • The body of a generator function is not executed until the next function is called on the generator object.
    • Execution of the function body pauses at the yield statement.
    • The generator still remembers all of the environment of the function execution.
  • Generators enable us to customize the computation for each element in the iterator

Generators and Iterators

  • Generators return generators but also process iterators

The yield from statement

  • A generator may yield all values from an iterator using the yield from statement
  • Ex: Write a generator to combine two lists
1
2
3
4
5
def a_then_b(a ,b):
    yield from a
    yield from b

list(a_then_b([1,2], [3,4]))
1
[1, 2, 3, 4]
  • The yield from statement may also be used to retrieve all values generated by a generator
  • Ex: Write a generator for a reversed, consecutive sequence of numbers
1
2
3
4
5
6
7
8
def countdown(k):
    if k > 0:
        yield k
        yield from countdown(k-1)
    else:
        yield 'Blast off!'

list(countdown(5))
1
[5, 4, 3, 2, 1, 'Blast off!']
  • Ex: Write generators that obtains all prefixes of a word, and also another generator that includes all substrings of a word
1
2
3
4
5
6
7
8
9
10
11
12
def prefixes(s):
    if s:
        yield s
        yield from prefixes(s[:-1])

def substrings(s):
    if s:
        yield from prefixes(s)
        yield from substrings(s[1:])

print("prefixes of 'stop'", list(prefixes('stop')))
print("substrings of 'stop'", list(substrings('stop')))
1
2
prefixes of 'stop' ['stop', 'sto', 'st', 's']
substrings of 'stop' ['stop', 'sto', 'st', 's', 'top', 'to', 't', 'op', 'o', 'p']

Ex: Partitions

  • A partition of positive integer n, using parts up to size m, is a way in which n can be expressed as the sum of positive integer parts up to m in increasing integer
1
2
3
4
5
6
7
8
9
10
11
12
def list_partitions(n, m):
    if n < 0 or m == 0:
        return []
    else:
        exact_matches = []
        if m == n:
            exact_matches = [str(m)]
        with_m = [str(m) + " + " + p for p in list_partitions(n-m,m)]
        without_m = list_partitions(n, m-1)
        return exact_matches + with_m + without_m
    
list_partitions(6, 4)
1
2
3
4
5
6
7
8
9
['4 + 2',
 '4 + 1 + 1',
 '3 + 3',
 '3 + 2 + 1',
 '3 + 1 + 1 + 1',
 '2 + 2 + 2',
 '2 + 2 + 1 + 1',
 '2 + 1 + 1 + 1 + 1',
 '1 + 1 + 1 + 1 + 1 + 1']
  • We may reimplement this using yield statements
1
2
3
4
5
6
7
8
9
def partitions(n, m):
    if n > 0 and m > 0:
        if m == n:
            yield str(m)
        for methods in partitions(n-m, m):
            yield str(m) + " + " + methods
        yield from partitions(n, m-1)
    
list(partitions(6, 4))
1
2
3
4
5
6
7
8
9
['4 + 2',
 '4 + 1 + 1',
 '3 + 3',
 '3 + 2 + 1',
 '3 + 1 + 1 + 1',
 '2 + 2 + 2',
 '2 + 2 + 1 + 1',
 '2 + 1 + 1 + 1 + 1',
 '1 + 1 + 1 + 1 + 1 + 1']
1
2
3
4
5
6
7
8
9
10
## Problem: Recursive Generators for Sequences

### Task
Implement a generator function `fibonacci(n)` that yields the first `n` numbers in the Fibonacci sequence. The Fibonacci sequence is defined as follows:
- The first two numbers are 0 and 1.
- Each subsequent number is the sum of the previous two numbers.

### Example
```python
list(fibonacci(6))  # Output: [0, 1, 1, 2, 3, 5]

Requirements

  • Use recursion to implement the generator.
  • Do not use any loops (for, while, etc.).
  • The generator should yield each Fibonacci number one by one.

Hints

  • You may define a helper function within fibonacci(n) to handle the recursion.
  • Remember to use the yield keyword to produce values from the generator. ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Rewrite this so that the generator yields in reverse order
def fibonacci(n):
    if n == 1:
        yield 0
    if n == 2:
        yield 1
        yield 0
    else:
        yield next(fibonacci(n-1)) + next(fibonacci(n-2))
        yield from fibonacci(n-1)

def fibonacci(n):
    def fib_helper(a, b, count):
        if count > 0:
            yield a
            yield from fib_helper(b, a + b, count - 1)
            

    return fib_helper(0, 1, n)
        
    
list(fibonacci(10))
1
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
1
2
import inspect
inspect.getmro(type(fibonacci(10)))
1
(generator, object)
1
2
3
4
5
6
7
8
9
10
11
def fib_generator():
    a, b=  0, 1
    while True:
        yield a
        a, b = b, a+b
    

t = fib_generator()

for i in range(10):
    print(next(t))
1
2
3
4
5
6
7
8
9
10
0
1
1
2
3
5
8
13
21
34
This post is licensed under CC BY 4.0 by the author.

CS61A: Lecture 15

CS61A: Lecture 16