CISC-280 Homework #2: Due September 13.

Practice with linear recursion and iteration.

Since the only data structure we know now are numbers, we can practice recursion and iteration by operating on the digits that make up numbers. Later in the course, we'll use very similar ideas on a general data structure called a list.

Any positive number n can be written as a sequence of digits dk dk-1 ...d1 d0, where dk is non-zero. Thus the number n is the sum of di times 10 to the i power, for i = 0 to k. For example, if n = 456, then k is 2 and d2 is 4, d1 is 5, and d0is 6. A Scheme program can access the individual digits by using the following basic functions:

(define (units-digit n)
       (remainder n 10))

(define (all-but-units-digit n)
        (quotient n 10))

With those definitions in place, for example,

(unit-digit 456) --> 6
(all-but-units-digit 456) --> 45

By combining these functions, you can access the rightmost (units) digit, the second digit, the third digit, etc. ultimately reaching the most significant (leftmost) digit. If (all-but-the-units-digit n) is zero, you know n is a one-digit number.

Using these access functions, define the following functions:

  1. (decimal-length n) returns k+1, where dkis the leading digit of n. For example,
    (decimal-length 348567) --> 6
  2. (ith-digit n i) returns di of n. For example
    (ith-digit 671835629 3) --> 5
  3. (leading-digit n) returns dk, the most significant digit.
    (leading-digit 534) --> 5
  4. (occurances d n) returns the number of times digit d occurs in n.
    (occurrances 6 5494576548754987587654655478563) --> 4
  5. (digit-sum n) returns the sum of the digits in n.
    (digit-sum 2354) --> 14, (digit-sum 8) --> 8
  6. (digital-root n) returns the result of repeatedly applying digit-sum until the result is a single digit.
    (digital-root 341943) --> 6 (via 3+4+1+9+4+3=24, then 2+4=6)

Remember to write smart test cases before you write the procedures. Hand in a printout of your definitions and tests, with each procedure documented briefly, and mention for each one if it implements a linear recursive procedure or an iterative procedure.

   7.  Alyssa P. Hacker doesn't see why if needs to be provided as a special form. "Why can't I just define it as an ordinary procedure in terms of cond?" she asks. Alyssa's friend Eva Lu Ator claims this can indeed be done, and she defines a new version of if:

(define new-if
   (lambda (predicate then-clause else-clause)
      (cond (predicate then-clause)
            (else else-clause))))

Eva demonstrates the program for Alyssa:

(new-if (= 2 3) 0 5) --> 5

(new-if (= 1 1) 0 5) --> 0

Delighted, Alyssa uses new-if to rewrite the factorial program:

(define factorial
   (lambda (n)
      (new-if (= n 1)
              1
              (* n (factorial (- n 1)))))

What happens when Alyssa attempts to use this definition to compute facorials? Explain.

8. The Scheme function time allows you to discover the amount of time a procedure takes to execute.
You call it by wrapping it around the call to the procedure that you wish to time, e.g.

(time (fast-expt 2 100000000))

Use time to compare the running time of the iterative version of expt-iter and fast-expt. Show your experimental results in a graph. Do your results bear out the order statistics that we discussed in class for these two algorithms?  Are your results compatible with the notion that programs on your machine run in time proportional to the number of steps required for the computation?

9. [worth 20 points] Note that fast-expt, even though it uses log n steps, is still a recursive procedure describing a linear recursive process. Design a procedure that creates an iterative process via tail recursion (hence, constant or Θ(1) space) that still uses only Θ(log n) steps. As is often the case with iterative algorithms, this is not quite as straightforward as the non-tail-recursive version.

Hints: Observe that (b^[n/2] )^2 = (b^2 )^[n/2] . Use three state variables: exponent n, base b, and an additional state variable a. Define the state transition (each time around the loop) in such a way that the product ab^n is unchanged from state to state. At the beginning, a = 1, and at the end a is the final result.

This technique of defining an invariant quantity (which remains unchanged from state to state) is a powerful, general way of thinking about the design of iterative looping algorithms.