## Creative Exercises

**2.1.22** *Birthday problem.* Compose a program with appropriate functions for studying the birthday problem (see EXERCISE 1.4.35).

**2.1.23** Euler’s totient function. Euler’s totient function is an important function in number theory: φ(*n*) is defined as the number of positive integers less than or equal to *n* that are relatively prime with *n* (no factors in common with *n* other than 1). Compose a function that takes an integer argument *n* and returns φ(*n*). Include global code that takes an integer from the command line, calls the function, and writes the result.

**2.1.24** *Harmonic numbers*. Create a program `harmonic.py` that defines three functions `harmonic()`, `harmonicSmall()`, and `harmonicLarge()` for computing the harmonic numbers. The `harmonicSmall()` function should just compute the sum (as in PROGRAM 2.1.1), the `harmonicLarge()` function should use the approximation H* _{n}* = log

*(*

_{e}*n*) + γ + 1/(2

*n*) − 1/(12

*n*

^{2}) + 1/(120

*n*

^{4}) (the number γ = 0.577215664901532... is known as

*Euler’s constant*), and the

`harmonic()`function should call

`harmonicSmall()`for

*n*< 100 and

`harmonicLarge()`otherwise.

**2.1.25** *Gaussian random values.* Experiment with the following function for generating random variables from the Gaussian distribution, which is based on generating a random point in the unit circle and using a form of the Box-Muller formula (see EXERCISE 1.2.24).

def gaussian(): r = 0.0 while (r >= 1.0) or (r == 0.0): x = -1.0 + 2.0 * random.random() y = -1.0 + 2.0 * random.random() r = x*x + y*y return x * math.sqrt(-2.0 * math.log(r) / r)

Take a command-line argument `n` and generate `n` random numbers, using an array `a[]` of 20 integers to count the numbers generated that fall between `i*.05` and `(i+1)*.05` for `i` from `0` to `19`. Then use `stddraw` to plot the values and to compare your result with the normal bell curve. *Remark*: This approach is faster and more accurate than the one described in EXERCISE 1.2.24. Although it involves a loop, the loop is executed only 4 / *π* (about 1.273) times on average. This reduces the overall expected number of calls to transcendental functions.

**2.1.26** *Binary search.* A general function that we study in detail in SECTION 4.2 is effective for computing the inverse of a cumulative distribution function like `cdf()`. Such functions are continuous and nondecreasing from (0,0) to (1,1). To find the value *x*_{0} for which *f*(*x*_{0}) = *y*_{0}, check the value of *f*(0.5). If it is greater than *y*_{0}, then *x*_{0} must be between 0 and 0.5; otherwise, it must be between 0.5 and 1. Either way, we halve the length of the interval known to contain *x*_{0}. Iterating, we can compute *x*_{0} to within a given tolerance. Add a function `cdfInverse()` to `gauss.py` that uses binary search to compute the inverse. Change the global code to take a number *p* between 0 and 100 as a third command-line argument and write the minimum score that a student would need to be in the top *p* percent of students taking the SAT in a year when the mean and standard deviation were the first two command-line arguments.

**2.1.27** *Black-Scholes option valuation.* The Black-Scholes formula supplies the theoretical value of a European call option on a stock that pays no dividends, given the current stock price *s*, the exercise price *x*, the continuously compounded risk-free interest rate *r*, the standard deviation σ of the stock’s return (volatility), and the time (in years) to maturity *t*. The value is given by the formula *s* Φ(*a*) − *xe* ^{−rt} Φ(*b*), where Φ(*z*) is the Gaussian cumulative distribution function, *a* = (ln(*s/x*) + (*r* + σ^{2}/2) *t*) / (), and *b* = *a* − . Compose a program that takes `s`,
`x`, `r`, `sigma`, and `t` from the command line and writes the Black-Scholes value.

**2.1.28** *Implied volatility.* Typically the volatility is the unknown value in the Black-Scholes formula. Compose a program that reads `s`, `x`, `r`, `t`, and the current price of the European call option from the command line and uses binary search (see EXERCISE 2.1.26) to compute σ.

**2.1.29** *Horner’s method.* Compose a program `horner.py` with a function `evaluate(x, a)` that evaluates the polynomial *a*(*x*) whose coefficients are the elements in the array `a[]`:

*a*_{0}+*a*_{1}*x*^{1}+*a*_{2}*x*^{2}+*...*+*a*_{n−2}*x*^{n−2}+*a*_{n−1}*x*^{n−1}

Use *Horner’s method*, an efficient way to perform the computations that is suggested by the following parenthesization:

*a*_{0}+*x*(*a*_{1}+*x*(*a*_{2}+ ... +*x*(*a*_{n−2}+*xa*_{n−1}) ...))

Then compose a function `exp()` that calls `evaluate()` to compute an approximation to *e*^{x}, using the first *n* terms of the Taylor series expansion *e*^{x} = 1 + x + x^{2}/2! + *x*^{3}/3! + ... Take an argument `x` from the command line, and compare your result against that computed by `math.exp(x)`.

**2.1.30** *Benford’s law.* The American astronomer Simon Newcomb observed a quirk in a book that compiled logarithm tables: the beginning pages were much grubbier than the ending pages. He suspected that scientists performed more computations with numbers starting with 1 than with 8 or 9, and postulated the first digit law, which says that under general circumstances, the leading digit is much more likely to be 1 (roughly 30%) than 9 (less than 4%). This phenomenon is known as *Benford’s law* and is now often used as a statistical test. For example, IRS forensic accountants rely on it to discover tax fraud. Compose a program that reads in a sequence of integers from standard input and tabulates the number of times each of the digits 1–9 is the leading digit, breaking the computation into a set of appropriate functions. Use your program to test the law on some tables of information from your computer or from the web. Then, compose a program to foil the IRS by generating random amounts from $1.00 to $1,000.00 with the same distribution that you observed.

**2.1.31** *Binomial distribution.* Compose a function `binomial()` that accepts an integer `n`, an integer `k`, and a float `p`, and computes the probability of obtaining exactly `k` heads in `n` biased coin flips (heads with probability `p`) using the formula

*f*(*k,n,p*) =*p*^{k}(1−*p*)^{n−k}*n*!/(*k*!(*n−k*)!)

*Hint*: To avoid computing with huge integers, compute *x* = ln *f* (*k,n,p*) and then return *e*^{x}. In the global code, take `n` and `p` from the command line and check that the sum over all values of `k` between 0 and `n` is (approximately) 1. Also, compare every value computed with the normal approximation

*f*(*k*,*n*,*p*) ≈ Φ(*k*+ 1/2,*np*, − Φ(*k*1/2,*np*,

**2.1.32** *Coupon collecting from a binomial distribution.* Compose a version of `getCoupon()` that uses `binomial()` from the previous exercise to return coupon values according to the binomial distribution with *p* = 1/2. *Hint*: Generate a uniformly distributed random number *x* between 0 and 1, then return the smallest value of *k* for which the sum of *f* (*j*, *n*, *p*) for all *j* < *k* exceeds *x*. *Extra credit*: Develop a hypothesis for describing the behavior of the coupon collector function under this assumption.

**2.1.33** *Chords.* Compose a version of `playthattunedeluxe.py` that can handle songs with chords (three or more different notes, including harmonics). Develop an input format that allows you to specify different durations for each chord and different amplitude weights for each note within a chord. Create test files that exercise your program with various chords and harmonics, and create a version of *Für Elise* that uses them.

**2.1.34** *Postal barcodes.* The barcode used by the U.S. Postal System to route mail is defined as follows: Each decimal digit in the ZIP code is encoded using a sequence of three half-height and two full-height bars. The barcode starts and ends with a full-height bar (the guard rail) and includes a checksum digit (after the five-digit ZIP code or ZIP+4), computed by summing up the original digits modulo 10. Define the following functions:

- Draw a half-height or full-height bar on
`stddraw`. - Given a digit, draw its sequence of bars.
- Compute the checksum digit.

Also define global code that reads in a five- (or nine-) digit ZIP code as the command-line argument and draws the corresponding postal barcode.

**2.1.35** *Calendar.* Compose a program `cal.py` that takes two command-line arguments `m` and `y` and writes the monthly calendar for the `m`th month of year `y`, as in this example:

% python cal.py 2 2015 February 2015 S M Tu W Th F S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

*Hint*: See `leapyear.py` (PROGRAM 1.2.5) and EXERCISE 1.2.26.

**2.1.36** *Fourier spikes.* Compose a program that takes a command-line argument *n* and plots the function

- (cos(
*t*) + cos(2*t*) + cos(3*t*) + . . . + cos(*N t*)) /*N*

for 500 equally spaced samples of *t* from −10 to 10 (in radians). Run your program for *n* = 5 and *n* = 500. *Note*: You will observe that the sum converges to a spike (0 everywhere except a single value). This property is the basis for a proof that *any* smooth function can be expressed as a sum of sinusoids.