Q1: Has Seven
Write a recursive function has_seven that takes a positive integer n and returns whether n contains the digit 7. Use recursion - the tests will fail if you use any assignment statements.
1 | def has_seven(k): |
1 | # iteration |
Q2: Ping-pong
The ping-pong sequence counts up starting from 1 and is always either counting up or counting down. At element k, the direction switches if k is a multiple of 7 or contains the digit 7. The first 30 elements of the ping-pong sequence are listed below, with direction swaps marked using brackets at the 7th, 14th, 17th, 21st, 27th, and 28th elements:
1 2 3 4 5 6 [7] 6 5 4 3 2 1 [0] 1 2 [3] 2 1 0 [-1] 0 1 2 3 4 [5] [4] 5 6
Implement a function pingpong that returns the nth element of the ping-pong sequence without using any assignment statements.
You may use the function has_seven from the previous problem.
Hint: If you’re stuck, first try implementing pingpong using assignment statements and a while statement. Then, to convert this into a recursive solution, write a helper function that has a parameter for each variable that changes values in the body of the while loop.
1 | def pingpong(n): |
1 | # iteration |
1 | # Alternate solution 1 |
This is a fairly involved recursion problem, which we will first solve through iteration and then convert to a recursive solution.
Note that at any given point in the sequence, we need to keep track of the current value of the sequence (this is the value that might be output) as well as the current index of the sequence (how many items we have seen so far, not actually output).
For example, 14th element has value 0, but it’s the 14th index in the sequence. We will refer to the value as x and the index as i. An iterative solution may look something like this:
1 | def pingpong(n): |
Hopefully, it is clear to you that this has a big problem. This doesn’t account for changes in directions at all! It will work for the first seven values of the sequence, but then fail after that. To fix this, we can add in a check for direction, and then also keep track of the current direction to make our lives a bit easier (it’s possible to compute the direction from scratch at each step, see the direction function in the alternate solution).
1 | def pingpong(n): |
All that’s left to do is to write the next_dir function, which will take in the current direction and index and then tell us what direction to go in next (which could be the same direction):
1 | def next_dir(is_up, i): |
There’s a tiny optimization we can make here. Instead of calculating an increment based on the value of is_up, we can make it directly store the direction of change into the variable (next_dir is also updated, see the solution for the new version):
1 | def pingpong(n): |
This will work, but it uses assignment. To convert it to an equivalent recursive version without assignment, make each local variable into a parameter of a new helper function, and then add an appropriate base case. Lastly, we seed the helper function with appropriate starting values by calling it with the values we had in the iterative version.
You should be able to convince yourself that the version of pingpong in the solutions has the same logic as the iterative version of pingpong above.
Video walkthrough: https://youtu.be/74gwPjgrN_k
Several doctests refer to these functions:
from operator import add, mul, sub
1 | square = lambda x: x * x |
Q3: Filtered Accumulate
Extend the accumulate function from homework 2 to allow for filtering the results produced by its term argument by filling in the implementation for the filtered_accumulate function:
1 | def filtered_accumulate(combiner, base, pred, n, term): |
filtered_accumulate has the following parameters:
combiner, base, term and n: the same arguments as accumulate.
pred: a one-argument predicate function applied to the values of term(k) for each k from 1 to n. Only values for which pred returns a true value are included in the accumulated total. If no values satisfy pred, then base is returned.
For example, the result of filtered_accumulate(add, 0, is_prime, 11, identity) is
0 + 2 + 3 + 5 + 7 + 11
for a valid definition of is_prime.
Implement filtered_accumulate by defining the combine_if function. Exactly what this function does is something for you to discover. Do not use any loops or make any recursive calls to filtered_accumulate.
Hint: The order in which you pass the arguments to combiner in your solution to accumulate matters here.