391 lines
12 KiB
Plaintext
391 lines
12 KiB
Plaintext
File: MULISP5.LES (c) 12/27/85 Soft Warehouse, Inc.
|
||
|
||
CLRSCRN
|
||
This is muLISP programming lesson #5. This lesson introduces the primitive
|
||
muLISP numerical functions and presents several techniques useful for writing
|
||
efficient mathematical functions.
|
||
|
||
muLISP provides the user with both infinite precision integer arithmetic and
|
||
adjustable precision rational arithmetic. This means that the only limit on
|
||
the size of integers is available computer memory. Integers consisting of
|
||
thousands of digits are possible. This makes muLISP useful for investigations
|
||
in the fields of number theory and cryptography.
|
||
|
||
By default the precision muLISP uses for rational arithmetic provides about
|
||
7 digits of accuracy. This approximates the accuracy of single precision
|
||
floating point arithmetic. The description of the function PRECISION in
|
||
Chapter 5 of the muLISP Reference Manual provides details on how to increase
|
||
the precision used for rational arithmetic.
|
||
|
||
|
||
CONTINUE
|
||
We begin by discussing the primitive numerical functions muLISP provides.
|
||
The functions +, -, *, and / denote addition, subtraction, multiplication,
|
||
and division respectively. In most conventional programming languages, you
|
||
enter numerical expressions using infix notation (i.e. operators appear
|
||
between their operands). For example:
|
||
|
||
3*4 + 5
|
||
|
||
Since LISP is a functional programming language, functional notation is more
|
||
natural and consistent for numerical expressions. For example, the above
|
||
expression is entered as:
|
||
|
||
$ (+ (* 3 4) 5)
|
||
|
||
During this break, familiarize yourself with these four numerical functions.
|
||
When ready to return to the lesson, type (RETURN) and press the <RETURN> key.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
One advantage of functional notation over operator notation is that you are
|
||
not limited to two operands for each operation. The functions +, *, -,
|
||
and / can accept a variable number of arguments. For example:
|
||
|
||
$ (+ 45 -23 57)
|
||
|
||
$ (* 1 2 3 4 5)
|
||
|
||
During this break using test cases, empirically determine what the functions
|
||
- and / do when called with more than two arguments.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
If a numerical function is inadvertently called with nonnumeric arguments, the
|
||
"Nonnumeric Argument" error message will be displayed on the console followed
|
||
by the errant function call and the option prompt
|
||
|
||
Abort, Break, Continue, Top-level, Restart, System?
|
||
|
||
You then must select one of the five options by entering its first letter.
|
||
The description of the function BREAK in Chapter 5 of the muLISP Reference
|
||
Manual describes the options in detail. The next screen summarizes the
|
||
options available.
|
||
|
||
CONTINUE
|
||
Summary of the error break options:
|
||
|
||
Abort: aborts execution, returns control directly to the current level
|
||
of the read-eval-print loop, and prompts the user for further input.
|
||
|
||
Break: temporarily suspends execution and prompts the user for input. The
|
||
errant function can be determined by examining the value of the variable
|
||
BREAK. To return from the break, type (RETURN expn) where <expn> is the
|
||
value you want to be returned as the value of the errant function call.
|
||
|
||
Continue: continues execution with the errant function call used as the value
|
||
returned.
|
||
|
||
Top-level: aborts execution, returns control to the top-level read-eval-
|
||
print loop, and prompts the user for further input.
|
||
|
||
Restart: abandons the current muLISP environment and starts up a fresh muLISP
|
||
system.
|
||
|
||
System: terminates muLISP and returns to the host operating system.
|
||
|
||
|
||
CONTINUE
|
||
Enough of errors, let's return to mathematics! The factorial of a number is
|
||
of great importance in probability theory. The factorial of a nonnegative
|
||
integer N, denoted N!, can be recursively defined as follows:
|
||
|
||
0! = 1,
|
||
N! = N*(N-1)! if N > 0
|
||
|
||
The equivalent muLISP definition of FACTORIAL is:
|
||
|
||
$ (DEFUN FACTORIAL (N)
|
||
((EQL N 0) 1)
|
||
(* N (FACTORIAL (- N 1))) )
|
||
|
||
$ (FACTORIAL 5)
|
||
|
||
This is an efficient definition; however, if N is large there is the
|
||
possibility of a memory space exhausted error because of a stack-overflow.
|
||
During this break, write and test an iterative version of FACTORIAL. Hint:
|
||
you will need an accumulation variable for the result.
|
||
|
||
BREAK
|
||
An iterative definition for FACTORIAL and an example:
|
||
|
||
$ (DEFUN FACTORIAL (N
|
||
% Local: % RSLT)
|
||
(SETQ RSLT 1)
|
||
(LOOP
|
||
((ZEROP N) RSLT)
|
||
(SETQ RSLT (* N RSLT))
|
||
(SETQ N (- N 1)) ) )
|
||
|
||
$ (FACTORIAL 100)
|
||
|
||
As the example illustrates, muLISP can handle very large numbers. In the
|
||
definition we introduced the primitive recognizer function ZEROP. (ZEROP N)
|
||
is equivalent to (EQL N 0) but is slightly more efficient.
|
||
|
||
CONTINUE
|
||
A series that keeps turning up in nature in the strangest places is the
|
||
Fibonacci Series. The Nth Fibonacci number, denoted F(N), can be recursively
|
||
defined as follows:
|
||
|
||
F(0) = 1,
|
||
F(1) = 1,
|
||
F(N) = F(N-1) + F(N-2) if N > 1.
|
||
|
||
The equivalent muLISP definition of FIBONACCI is:
|
||
|
||
$ (DEFUN FIBONACCI (N)
|
||
((ZEROP N) 1)
|
||
((EQL N 1) 1)
|
||
(+ (FIBONACCI (- N 1)) (FIBONACCI (- N 2))) )
|
||
|
||
$ (FIBONACCI 10)
|
||
|
||
During this break, gain a feel for the efficiency of the above algorithm by
|
||
calling FIBONACCI with progressively larger arguments.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
As you test cases should have demonstrated, this is an extremely inefficient
|
||
algorithm. The problem is that to compute the Nth Fibonacci number, the
|
||
(N-2)th Fibonacci number is unnecessarily computed twice, the (N-3)th three
|
||
times, the (N-4)th five times, and it keeps gets getting worse.
|
||
|
||
Since this is a recursive algorithm, most people jump to the conclusion that
|
||
recursion is the problem, and set about writing a messy iterative definition
|
||
to achieve efficiency. But the problem is not recursion but the algorithm.
|
||
|
||
Rather than computing the Nth Fibonacci number by working backward toward
|
||
zero, the efficient way is to start at zero and work up to the Nth Fibonacci
|
||
number using two variables to store the two most recently computed Fibonacci
|
||
numbers.
|
||
|
||
During this break, use this approach to define an efficient, yet recursive,
|
||
definition for Fibonacci numbers calling it FIB. Compare the efficiency of
|
||
FIB with FIBONACCI.
|
||
|
||
BREAK
|
||
An efficient, recursive definition for Fibonacci numbers:
|
||
|
||
$ (DEFUN FIB (N F1 F2)
|
||
((ZEROP N) F1)
|
||
(FIB (- N 1) (+ F1 F2) F1) )
|
||
|
||
$ (FIB 10 1 0)
|
||
|
||
$ (FIBONACCI 10)
|
||
|
||
FIB is a function of 3 arguments. The first argument is N, the second must be
|
||
1, and the third 0. If you insist on a single argument Fibonacci function,
|
||
you can define a "front-end" function that merely calls FIB with the
|
||
appropriate second and third arguments.
|
||
|
||
For those of you still not convinced of the elegance and efficiency of
|
||
recursion, write an iterative definition for Fibonacci numbers and compare it
|
||
to the above definition. If you are a believer, you can simply continue on.
|
||
|
||
CONTINUE
|
||
Raising an expression to an integer power is certainly an important
|
||
mathematical operation. To raise N to Mth power all you have to do is
|
||
multiply N times itself M times.
|
||
|
||
During this break, write an iterative definition of POWER as a function of two
|
||
arguments.
|
||
|
||
BREAK
|
||
A iterative definition of POWER:
|
||
|
||
$ (DEFUN POWER (N M
|
||
% Local: % RSLT )
|
||
(SETQ RSLT 1)
|
||
(LOOP
|
||
((ZEROP M) RSLT)
|
||
(SETQ RSLT (* N RSLT))
|
||
(SETQ M (SUB1 M)) ) )
|
||
|
||
$ (POWER 2 10)
|
||
|
||
The call to the primitive function SUB1 in the above definition is equivalent
|
||
to, but slightly more efficient, than the call (- N 1) would be. ADD1 is also
|
||
a primitively defined muLISP function.
|
||
|
||
There is an even more efficient way of computing powers of numbers than this
|
||
iterative technique. On to the next screen!
|
||
|
||
|
||
CONTINUE
|
||
Consider the following facts:
|
||
|
||
1. If M is an even number, then N to Mth power is equal to N squared raised
|
||
to the M/2th power.
|
||
|
||
2. If M is odd, then N to Mth power is N times N raised to the (M-1)th power.
|
||
|
||
To implement this algorithm you will need the primitive recognizer function
|
||
EVENP. It returns T if and only if its argument is an even integer; otherwise
|
||
it returns NIL.
|
||
|
||
During this break, define POWER using the recursive squaring approach
|
||
described above.
|
||
|
||
BREAK
|
||
An efficient, recursive definition of POWER:
|
||
|
||
$ (DEFUN POWER (N M)
|
||
((ZEROP M) 1)
|
||
((EVENP M)
|
||
(POWER (* N N) (/ M 2)) )
|
||
(* N (POWER N (SUB1 M))) )
|
||
|
||
$ (POWER 10 100)
|
||
|
||
CONTINUE
|
||
The primitive function TRUNCATE is useful for converting ratios and quotients
|
||
to integers. If <n> is a number, (TRUNCATE n) truncates <n> to the nearest
|
||
integer in the direction of zero:
|
||
|
||
$ (TRUNCATE 7/3)
|
||
|
||
$ (TRUNCATE -0.95)
|
||
|
||
|
||
If called with two arguments, TRUNCATE returns their truncated integer
|
||
quotient. Note that (TRUNCATE n m) is equivalent to (TRUNCATE (/ n m)):
|
||
|
||
$ (TRUNCATE 7 3)
|
||
|
||
$ (TRUNCATE -46.3 5.2)
|
||
|
||
|
||
CONTINUE
|
||
TRUNCATE returns the truncated integer quotient of its two arguments. The
|
||
primitive function REM returns the corresponding remainder of its two
|
||
arguments. TRUNCATE and REM are defined so they observe the law between
|
||
quotients and remainders: If (TRUNCATE n m) returns the integer q and
|
||
(REM n m) returns the number r, then
|
||
|
||
n = q*m + r
|
||
|
||
Often it is useful to obtain both the quotient and remainder at the cost
|
||
of only one division. The primitive function DIVIDE returns cons of the
|
||
quotient and remainder of its two arguments.
|
||
|
||
$ (TRUNCATE 7 3)
|
||
|
||
$ (REM 7 3)
|
||
|
||
$ (DIVIDE 7 3)
|
||
|
||
|
||
CONTINUE
|
||
The Greatest Common Divisor (GCD) of two integers is the largest nonnegative
|
||
integer number that evenly divides both integers. Euclid's algorithm for the
|
||
GCD of the integers N and M can be paraphrased as:
|
||
|
||
1. If N = 0, then GCD (N, M) = M;
|
||
|
||
2. Otherwise, GCD (N, M) = GCD (M mod N, N).
|
||
|
||
During this break, define the function GCD using Euclid's algorithm and try it
|
||
out on some examples. Use the function REM to obtain M mod N.
|
||
|
||
BREAK
|
||
Recursive definition of GCD using Euclid's algorithm:
|
||
|
||
$ (DEFUN GCD (N M)
|
||
((ZEROP N) M)
|
||
(GCD (REM M N) N) )
|
||
|
||
$ (GCD 21 56)
|
||
|
||
Actually the functions GCD and LCM (Least Common Multiple) are primitively
|
||
defined in muLISP. Naturally the primitive versions are faster and can accept
|
||
an arbitrary number of arguments.
|
||
|
||
|
||
CONTINUE
|
||
Finally we need to mention the primitive numerical comparator functions: =,
|
||
/=, <, >, <=, and >=. For example:
|
||
|
||
$ (= 34 34.0) ;Equal test
|
||
|
||
$ (/= 3/4 0.75) ;Not equal test
|
||
|
||
$ (< 67 45) ;Less than test
|
||
|
||
$ (>= 19 17 17) ;Greater than or equal test
|
||
|
||
As the last example shows, the numerical comparator functions can be called
|
||
with more than two arguments. If called with nonnumeric arguments, these
|
||
functions will cause a "Nonnumeric Argument" error break.
|
||
|
||
|
||
CONTINUE
|
||
To determine if a number lies within a given interval, the function < can be
|
||
called with 3 arguments. For example, to determine if "g" is a lower case
|
||
letter enter:
|
||
|
||
$ (< 96 (ASCII 'g) 123)
|
||
|
||
The function ASCII returns the ASCII code if given a character name. It
|
||
returns the equivalent ASCII character if given a number between 0 and 256.
|
||
|
||
During this break, write the recognizer function LOWERCASE that determines if
|
||
a character is a lower case character.
|
||
|
||
BREAK
|
||
The LOWERCASE recognizer function:
|
||
|
||
$ (DEFUN LOWERCASE (CHAR)
|
||
(< 96 (ASCII CHAR) 123) )
|
||
|
||
$ (LOWERCASE 'g)
|
||
|
||
CONTINUE
|
||
Let's finish off this lesson by writing a function for sorting a list of
|
||
numbers into increasing order. We are not too concerned with efficiency so
|
||
let's use a simple "insertion sort" algorithm that is adequate for short
|
||
lists.
|
||
|
||
First we need a function that inserts an number in the proper place in a
|
||
sorted list of numbers. During this break, write INSERT-NUM, a function that
|
||
inserts NUM into LST. Use iteration or recursion depending on your taste.
|
||
|
||
BREAK
|
||
A recursively defined INSERT-NUM function:
|
||
|
||
$ (DEFUN INSERT-NUM (NUM LST)
|
||
((NULL LST)
|
||
(LIST NUM) )
|
||
((< NUM (CAR LST))
|
||
(CONS NUM LST) )
|
||
(CONS (CAR LST) (INSERT-NUM NUM (CDR LST))) )
|
||
|
||
$ (INSERT-NUM 12 '(5 9 11 14 19 21))
|
||
|
||
During this break, use INSERT-NUM to write the function NUMBER-SORT that sorts
|
||
a list of numbers.
|
||
|
||
BREAK
|
||
A recursive defined NUMBER-SORT function:
|
||
|
||
$ (DEFUN NUMBER-SORT (LST)
|
||
((NULL LST) NIL)
|
||
(INSERT-NUM (CAR LST) (NUMBER-SORT (CDR LST))) )
|
||
|
||
$ (NUMBER-SORT '(34 23 -14 27 56 22 83))
|
||
|
||
|
||
The built-in function SORT uses an efficient list merge sort. If <test> is
|
||
a comparator function, (SORT list test) sorts and returns <list> based on
|
||
<test>. For example:
|
||
|
||
$ (SORT '(34 23 -14 27 56 22 83) '<)
|
||
|
||
|
||
This concludes our discussion of numerical programming techniques using
|
||
muLISP. Congratulations on successfully completing lesson #5.
|
||
|
||
CONTINUE
|
||
$ (RDS)
|
||
|