CMPS 144L Spring 2020
Lab #9 (Week of April 20): Recursion

Activity #1: Numeral to Number Conversion

Here we confine ourselves to considering decimal integer numerals, by which we mean non-empty character strings (i.e., values of type String) containing only digit characters (i.e., char values in the range '0' .. '9').

Examples of numerals (expressed as String literals, as might appear in a Java program) are "7" and "80236".

The Integer.parseInt() method (recall that java.lang.Integer is the so-called wrapper class for the int primitive data type) has as its purpose to translate such numerals to their corresponding numbers (i.e., values of type int). Thus, for example, the call Integer.parseInt("5302") evaluates to the int value 5302.

As an academic exercise, in this activity you are to develop a method that performs the same translations. In addition, your solution must be recursive, meaning that it must involve a method that calls itself.

A recursive solution to this problem can be based upon the following ideas. Let #() be the (mathematical) function mapping (decimal integer) numerals into their corresponding integer values. For example, #("529") = 529. We can also imagine that # is overloaded so that it can be applied to digit characters, so that, for example, #('3') = 3.

Let x be the numeral x1x2...xn-1xn, (where each xi is a digit character).

If n=1 (i.e., the length of x is one), then #(x) = #(x1). That is, translating x into a number reduces to translating its lone digit into a number, which is easy. This is the base case.

If, on the other hand, n>1 (the recursive case):

#(x) = #(x1x2...xn-1xn) = 10 × #(x1x2...xn-1) + #(xn)

For example,

    #("286")

=     < recursive case applied to "286" >

    10×#("28") + #('6')

=     < recursive case applied to "28" >

    10×(10×#("2") + #('8')) + #('6')

=     < base case applied to "2" >

    10×(10×#('2') + #('8')) + #('6')

=     < # applied to each of '2', '8', and '6' >

    10×(10×2 + 8) + 6

=     < arithmetic >

    286 

Having understood all that, supply the body of the parseIntRec() method in the ParseIntRecTester Java application that is provided. Use the application to test your method for correctness. Entering an empty string at the prompt will cause the program to terminate.


Activity #2: Exponentiation

An efficient approach to computing a (real number) base x raised to a (nonnegative integer) power n follows from this recursive characterization of xn:

xn = { 1if n = 0
(x2)n/2if n>0 and n is even
xn-1 × xotherwise

Using this, supply the body of the xToTheNRec() method in the ExponentiationTester Java application that is provided. Use the application to test your method for correctness. Entering a negative exponent at the prompt will cause the program to terminate.

For each x and n that the user enters, the program will display the value computed by your method as well as that computed by the Math.pow() method. If the two values are not very close, it is a strong indication that your method is in error.

The program also displays the number of multiplication operations performed by your method. (Well, you are responsible for updating the relevant variable appropriately within your method to keep an accurate count.) If your method is a faithful implementation of the recursive equation given above, the number of multiplications should be at most 2×lg(n) (i.e., twice the logarithm, to the base 2, of the exponent). Compare that to n−1, which is the number of multiplications that you would perform by following the "naive" approach.


Activity #3: Greatest Common Divisor

Note: The next three paragraphs are taken largely from Wikipedia's Euclidean algorithm entry. End of note.

The Euclidean algorithm (described by the Greek mathematician Euclid in his mathematical treatise Elements in 300 BC) is based on the principle that the greatest common divisor (GCD) of two numbers does not change if the larger number is replaced by its difference with the smaller number. For example, 21 is the GCD of 252 and 105, and the same number 21 is also the GCD of 105 and 252 − 105 = 147. Since this replacement reduces the larger of the two numbers, repeating this process gives successively smaller pairs of numbers until the smaller of the two numbers becomes zero. When that occurs, the larger one is the GCD of that pair and hence the GCD of the original pair of numbers.

The version of the Euclidean algorithm described above (and by Euclid) can take many subtraction steps to find the GCD when the larger of the given numbers is much bigger than the other. A more efficient version of the algorithm shortcuts these steps, instead replacing the larger of the two numbers by its remainder when divided by the smaller of the two. (With this version, the algorithm stops when that remainder is zero.)

With this improvement, the algorithm never requires more steps than five times the number of digits (base 10) of the smaller integer. This was proven by Gabriel Lame in 1844, and marks the beginning of computational complexity theory (which is concerned, in part, with how much "work" an algorithm performs as a function of how much input data it is given).

In summary, assuming that k≥m≥0 and k+m > 0:

If m is zero, then GCD(k,m) = k; otherwise, GCD(k,m) = GCD(m, k%m).

Using this information, supply the body of the gcdViaRec() method in the GCD_Tester Java application that is provided. Use the application to test your method for correctness. Entering a negative value for k causes the program to terminate.


Activity #4: Recursive Lists

These three files provide a Java implementation of the "recursive list" abstract data type:

The Java class RecListLabUtils has some stubbed methods that are to be completed. Of course, they are intended to be recursive. Use the RecListLabUtilsTester application to perform tests. Running it will produce output that looks like the following:

Testing the numOccur() method...
With respect to the list [3 5 4 6 3 5 7 1 3 6 5 ]:
  0 occurs 0 times.
  1 occurs 1 times.
  2 occurs 0 times.
  3 occurs 3 times.
  4 occurs 1 times.
  ...
  ...

Testing the removeFirst() method...
With respect to the list [3 5 4 6 3 5 7 1 3 6 5 ]:
  Result of removing first occurrence of 1 is [3 5 4 6 3 5 7 3 6 5 ].
  Result of removing first occurrence of 2 is [3 5 4 6 3 5 7 1 3 6 5 ].
  Result of removing first occurrence of 3 is [5 4 6 3 5 7 1 3 6 5 ].
  ...
  ...

Testing the removeAll() method...
With respect to the list [3 5 4 6 3 5 7 1 3 6 5 ]:
  Result of removing all occurrences of 1 is [3 5 4 6 3 5 7 3 6 5 ].
  Result of removing all occurrences of 2 is [3 5 4 6 3 5 7 1 3 6 5 ].
  Result of removing all occurrences of 3 is [5 4 6 5 7 1 6 5 ].
  ...
  ...

Testing the isAscending() method...
isAscending([3 5 4 6 3 5 7 1 3 6 5 ]) is false
isAscending([1 3 4 6 7 ]) is true
isAscending([1 3 3 3 6 7 ]) is true