Description
This Lab exercise introduces you to a powerful problem solving method using functional recursion. Recursion refers to an abstraction that is defined in terms of itself. Examples include mathematical abstractions such as Fibonacci series, Factorials, and Exponentials, and other abstractions such as the Towers of Hanoi puzzle, the Eight Queens problem and Fractal Geometry. Many interesting and challenging computational problems that are difficult to solve in an iterative fashion are very simple and elegant to solve using recursive function calls. Following the methods discussed in class, you should begin to get a feel for “thinking recursively”.
Warm-up 1) Consider the following simple recursive function: def foo(n): if n<1: print('') else: print('*',end='') foo( n-1 ) Discuss the following questions with your lab partner(s). Do not attempt the remainder of this lab exercise until you are able to answer them and are comfortable with the concepts:
a. Describe what this function does, given an integer value for n.
b. Every recursive function must contain at least one base case. What is the base case for the function foo()? c. Why is this considered a "base case"?
d. Every recursive function includes a "reduction" step that always moves closer to one of the base cases. Will the reduction step in the foo() function move closer to the base case under all circumstances? Explain why or why not.
e. What distinguishes a "base case" from a "reduction step" in a recursive function definition?
2) Consider the following: "construct a recursive function to display the digits of an integer in reverse order on the console display". This is a non-pure function (procedure) that takes a single integer argument: def reverseNum(n): and, given an integer value such as: reverseNum(5786);
will output the following to the console display: 6875 Construct the reverseNum function: a. Start by identifying a base case that is trivial to solve. For this problem, perhaps the easiest integer to "reverse" would be any value with a single digit. In this case, you would simply display the input value and be done. Write the Python statement or statements to implement the base case and return.
b. Next, determine if there is some way the original problem can be represented as a simple sub-problem combined with some simple operation. For this particular problem, note that if the argument contains more than one digit, reversing it entails displaying the rightmost digit first, followed by the rest of the digits in reverse order. The "rest of the digits in reverse order" is simply a sub-problem of the original problem! First, write the Python statement or statements that will display the "rightmost" digit. c. Now, write the recursive reduction step that calls the function for the "rest of the digits".
d. Combining these two steps forms the complete reduction case. If this is the only reduction step, it is simply performed for any but the base case(s). Complete the else clause from a) to form the entire reduction step.
e. Before you complete and test the function, take a minute to ensure that a) the base case represents a correct solution, and b) the reduction step is guaranteed to get you "closer" to the base case. Now combine the base and reduction steps and write the complete function definition.
3) Write a recursive function to determine the maximum value in a list of integers (without using the built-in function max. The function declaration is: def maxValue( vals ): where vals is a list of integers. a. First, describe the base case: [hint: in what size list would it be easiest to find the maximum value? and which value would it be?]
b. Next, describe the reduction step: [hint: describe the original problem using a simple case in combination with some reduced form of the problem. Also consider how you would obtain a "smaller" list.
c. Describe why the reduction step will 1) solve the problem and 2) move closer to the base case: d. Now, write and test the complete function definition for maxValue
Stretch
1) Fibonacci Numbers Fibonacci numbers are integers derived from the Fibonacci Series:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, . . .
named for Leonardo Fibonacci who described it formally in the early 13th century. However, this series is traced to the mathematics of ancient India where it was used to describe patterns of Sanskrit syllables. More than a mathematical curiosity, Fibonacci numbers appear often in many biological patterns such as the spiral of seeds in sunflowers, the arrangement of pinecone and pineapple florets, the branching of trees and the number of petals on flowers to name just a few. Each of these biological patterns have their bases in the Fibonacci Sequence.
Mathematically, the Fibonacci sequence is described recursively: Fn = Fn-1 + Fn-2 F0 = 0 F1 = 1 Construct a recursive function named fibonacci that will take an integer value n as an argument and return Fn , and then write a short program that will call your function to print out the first 20 Fibonacci numbers.
2) Fractal Trees Consider the following function that uses recursion to draw a "tree" using Turtle graphics: def tree(t, trunkLength): if trunkLength < 5: return else: t.forward(trunkLength) t.right(30) tree(t, trunkLength-15) t.left(60) tree(t, trunkLength-15) t.right(30) t.backward(trunkLength)
Implement this function and try it out with a branch length of 100 (recall that you can speed up the drawing by using the .speed turtle method). It generates a symmetric drawing resembling a "tree". However no real trees actually grow this way! When a tree creates a new branch, there is some bounded randomness to the direction and length of each branch.
Modify the tree function so it will choose new branching angles between 15 and 45 degrees, and generate branches that are between 12 and 18 units shorter than their "parent" branches. [you will need to use the random module]
Now the output should look a bit more like a "real" tree. Go ahead and try different random ranges for the branches and angles. See if you can create more "lifelike" trees. It's fun!
Workout
1) Binomial Coefficient In probability and statistics applications, you often need to know the total possible number of certain outcome combinations. For example, you may want to know how many ways a 2-card BlackJack hand can be dealt from a 52 card deck, or you may need to know the number of possible committees of 3 people that can be formed from a 12-person department, etc. The binomial coefficient (often referred to as "n choose k") will provide the number of combinations of k things that can be formed from a set of n things. The binomial coefficient is represented mathematically as:
which we refer to as "n choose k". The naïve approach to computing the binomial coefficient is to use factorials:
However, this approach is computationally infeasible for all but the simplest problems because factorials quickly become too large to be represented by any numerical variables. For example if you wanted to know the number of BlackJack hands that can be dealt from 3 decks of cards shuffled together, the factorial (52*3)! is not computable using the naïve approach! Fortunately, the definition for the binomial coefficient can be expressed as a recurrence:
Write a recursive function named choose(n,k) that will compute and return the value of the binomial coefficient given the integer values n and k.
Write a short Python program that will input values for n and k, call your recursive function and then output the results.
Use your program to determine the number of 5-card poker hands that can be dealt from a 52 card deck.
Use your program to determine the number of 2-card blackjack hands that can be dealt from three 52 card decks all shuffled together.
Challenge Here is an interesting challenge problem. Try it if you have extra time or would like additional practice outside of lab.
1) Euclidean GCD The Greatest Common Divisor of two integers a and b, GCD(a,b), not both of which are zero, is the largest positive integer that divides both a and b. The Greek philosopher and mathematician Euclid devised an incredibly clever and efficient algorithm to determine the GCD of two integers using a minimum number of steps.
The Euclidean algorithm for finding the Greatest Common Divisor of a and b is as follows:
If b=0, then GCD(a,b) = a.
Otherwise, divide a by b to obtain an integer quotient q and remainder r. By definition, a = bq + r. Since GCD(a,b) = GCD(b,r), simply replace a with b and b with r and repeat the procedure.
Note carefully that the remainders are decreasing at each iteration, therefore a remainder of 0 will eventually result. The last nonzero remainder is GCD(a,b).
Example: GCD(1260,198): a=1260, b=198
GCD(1260,198) 1260 = 198 x 6 + 72 b=198, r=7 = GCD(198,72) GCD(198,72) 198 = 72 x 2 + 54 b=72, r=54 = GCD(72,54) GCD(72,54) 72 = 54 x 1 + 18 b=54, r=18 = GCD(54,18) GCD(54,18) 54 = 18 x 3 + 0 b=18, r=0 = GCD(18,0) GCD(18,0) = 18 (by rule 1) Write a recursive implementation of the Euclidean GCD function: gcd(a, b) Write a program that reads two integers and then calls a recursive implementation of the Euclidean GCD function: gcd(a, b)that calculates and returns the greatest common divisor of a and b. Display the two integers and the greatest common divisor on the terminal screen.
Note: a must be greater than or equal to b. If a < b, simply return GCD(b,a). If either a or b is negative, replace it with its absolute value.