Computing with Numbers
3.3 Accumulating Results: Factorial
Traceback (most recent call last):
File "quadratic.py", line 21, in ? main()
File "quadratic.py", line 14, in main discRoot = math.sqrt(b * b - 4 * a * c) ValueError: math domain error
The problem here is that b2 − 4ac < 0, and the sqrt function is unable to compute the square root of a negative number. Python prints a math domain error. This is telling us that negative numbers are not in the domain of the sqrt function. Right now, we don’t have the tools to fix this problem, so we will just have to assume that the user gives us solvable equations.
Actually, quadratic.py did not need to use the math library. We could have taken the square root using exponentiation **. (Can you see how?) Using math.sqrt is somewhat more efficient, and it allowed me to illustrate the use of the math library. In general, if your program requires a common mathematical function, the math library is the first place to look. Table 3.2 shows some of the other functions that are available in the math library.
Python Mathematics English
pi π An approximation of pi.
e e An approximation ofe.
sin(x) sin x The sine of x.
cos(x) cos x The cosine of x.
tan(x) tan x The tangent of x.
asin(x) arcsin x The inverse of sine x.
acos(x) arccos x The inverse of cosine x.
atan(x) arctan x The inverse of tangent x.
log(x) ln x The natural (basee) logarithm of x log10(x) log10x The common (base 10) logarithm of x.
exp(x) ex The exponential of x.
ceil(x) ⌈x⌉ The smallest whole number>= x floor(x) ⌊x⌋ The largest whole number<= x
Table 3.2: Some math library functions.
3.3 Accumulating Results: Factorial
Suppose you have a root beer sampler pack containing six different kinds of root beer. Drinking the various flavors in different orders might affect how good they taste. If you wanted to try out every possible ordering, how many different orders would there be? It turns out the answer is a surprisingly large number, 720. Do you know where this number comes from? The value 720 is the factorial of 6.
52 Chapter 3. Computing with Numbers In mathematics, factorial is often denoted with an exclamation (!). The factorial of a whole number n is defined as n! = n(n − 1)(n − 2) . . . (1). This happens to be the number of distinct arrangements for n items. Given six items, we compute 6! = (6)(5)(4)(3)(2)(1) = 720 possible arrangements.
Let’s write a program that will compute the factorial of a number entered by the user. The basic outline of our program follows an input, process, output pattern.
Input number to take factorial of, n Compute factorial of n, fact
Output fact
Obviously, the tricky part here is in the second step.
How do we actually compute the factorial? Let’s try one by hand to get an idea for the process.
In computing the factorial of 6, we first multiply6(5) = 30. Then we take that result and do another multiplication30(4) = 120. This result is multiplied by three 120(3) = 360. Finally, this result is multiplied by 2360(2) = 720. According to the definition, we then multiply this result by 1, but that won’t change the final value of720.
Now let’s try to think about the algorithm more generally. What is actually going on here? We are doing repeated multiplications, and as we go along, we keep track of the running product. This is a very common algorithmic pattern called an accumulator. We build up, or accumulate, a final value piece by piece. To accomplish this in a program, we will use an accumulator variable and a loop structure. The general pattern looks like this:
Initialize the accumulator variable Loop until final result is reached
update the value of accumulator variable
Realizing this is the pattern that solves the factorial problem, we just need to fill in the details.
We will be accumulating the factorial. Let’s keep it in a variable called fact. Each time through the loop, we need to multiply fact by one of the factorsn, (n − 1), . . . , 1. It looks like we should use a forloop that iterates over this sequence of factors. For example, to compute the factorial of 6, we need a loop that works like this:
fact = 1
for factor in [6,5,4,3,2,1]:
fact = fact * factor
Take a minute to trace through the execution of this loop and convince yourself that it works.
When the loop body first executes, fact has the value 1 and factor is 6. So, the new value of fact is1 ∗ 6 = 6. The next time through the loop, factor will be 5, and fact is updated to 6 ∗ 5 = 30.
The pattern continues for each successive factor until the final result of 720 has been accumulated.
The initial assignment of 1 to fact before the loop is essential to get the loop started. Each time through the loop body (including the first), the current value of fact is used to compute the next value. The initialization ensures that fact has a value on the very first iteration. Whenever you
3.3. Accumulating Results: Factorial 53 use the accumulator pattern, make sure you include the proper initialization. Forgetting this is a common mistake of beginning programmers.
Of course, there are many other ways we could have written this loop. As you know from math class, multiplication is commutative and associative, so it really doesn’t matter what order we do the multiplications in. We could just as easily go the other direction. You might also notice that including 1 in the list of factors is unnecessary, since multiplication by1 does not change the result.
Here is another version that computes the same result:
fact = 1
for factor in [2,3,4,5,6]:
fact = fact * factor
Unfortunately, neither of these loops solves the original problem. We have hand-coded the list of factors to compute the factorial of six. What we really want is a program that can compute the factorial of any given inputn. We need some way to generate an appropriate sequence of factors from the value ofn.
Luckily, this is quite easy to do using the Python range function. Recall that range(n) produces a sequence of numbers starting with 0 and continuing up to, but not including, n. There are other variations of range that can be used to produce different sequences. With two parameters, range(start,n) produces a sequence that starts with the value start and continues up to, but does not include, n. A third version range(start, n, step) is like the two-parameter version, except that it uses step as the increment between numbers. Here are some examples:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(5,10)) [5, 6, 7, 8, 9]
>>> list(range(5, 10, 3)) [5, 8]
Given our input value n we have a couple of different range commands that produce an appro-priate list of factors for computing the factorial of n. To generate them from smallest to largest (a la our second loop), we could use range(2,n+1). Notice how I used n+1 as the second parameter, since the range will go up to but not include this value. We need the +1 to make sure that n itself is included as the last factor.
Another possibility is to generate the factors in the other direction (a la our first loop) using the three-parameter version of range and a negative step to cause the counting to go backwards:
range(n,1,-1). This one produces a list starting with n and counting down (step -1) to, but not including 1.
Here then is one possible version of the factorial program:
# factorial.py
54 Chapter 3. Computing with Numbers
# Program to compute the factorial of a number
# Illustrates for loop with an accumulator def main():
n = eval(input("Please enter a whole number: ")) fact = 1
for factor in range(n,1,-1):
fact = fact * factor
print("The factorial of", n, "is", fact) main()
Of course, there are numerous other ways this program could have been written. I have already mentioned changing the order of factors. Another possibility is to initialize fact to n and then use factors starting at n − 1 (as long as n > 0). You might try out some of these variations and see which one you like best.