cond and if), and
recursion in LISP.
Any or all components of a list can, in turn, be lists:
Since combinations of car and cdr are frequently used,
all combinations up to four uses of car and cdr are
defined as functions of the form cxxxr:
The LISP language includes the expressions "and", "or",
and "not". When these are applied to Boolean values ( t
and NIL), the results follow the conventions from mathematics.
Thus, (and A B) is true if both A and B are true
and false otherwise. (or A B) is true (t) if either
A is true or B is true (or both). not applied
to any true value produces false (NIL), and (not) applied
to false (NIL) returns true (t).
More generally, "and" and "or" may take as many arguments
as desired. "and" is true if all arguments are true, and
"or" is true if any of the arguments is true.
Exercises:
Exercises:
Exercises:
Much processing in LISP utilizes recursion; the body of a procedure
includes one or more calls to the very same procedure -- calls that deal
with simpler and more readily computable values of the parameters.
For instance, imagine that you want to define a procedure
If there is a tie for the longest string, let's say that whichever one
appears first on the list wins:
In starting to write this procedure, the first step is to determine which
of two given strings is longer. This is accomplished easily utilizing
LISP's built-in procedure
With this helper fuction, we consider how to search a list containing an
arbitrary number of strings. One simple case immediately comes to mind:
When
While LISP's syntax may seem a bit different from other languages you may
know, the approach here utilizes recursion in a rather common way.
Exercises:
Lists
In LISP, a list is written by enclosing the
elements of the list in parentheses. Here are some simple examples:
(+ x y)
(this is a list)
(sqrt x)
()
These lists have 3, 4, 2, and 0 components, respectively. The list
() with 0 components also is called the empty list or the
null list. In LISP, the null list may be represented as either
() or NIL.
(* pi (expt r 2))
(+ (* x x) (* 3 x) 2)
(all x (if (human x) (mortal x)))
( () )
((()))
((()())())
In reviewing the second of these examples, (+ (* x x) (* 3 x) 2) is
a list with four components:
+
(* x x)
(* 3 x)
2
List Extraction Functions:
Parts of lists can be extracted by car and cdr:
Examples:
(car '(a b c)) = a
(car '((a) b c)) = (a)
(car (car '((a) b c))) = a
(car 'a) error: a is not a list.
(car '()) error: () is not a pair.
(cdr '(a b c)) = (b c)
(cdr '((a) b c)) = (b c)
(cdr (cdr '(a b c))) = (c)
(cdr (car '((a) b c))) = ()
(cdr 'a) error: a is not a list.
(cdr '()) error: () is not a pair.
In each of these examples, note that car and cdr are applied
to a list which is introduced with a quote. Since parentheses are used
both to group data into a list and to call procedures, the LISP
interactive interface needs to see a single quote at the front of a list
literal, so that it will treat it as a datum instead of evaluating it.
(caar x) = (car (car x))
(cadr x) = (car (cdr x))
(caddr x) = (car (cdr (cdr x)))
List Constructor Function:
Two basic functions that construct new list structures are the functions
cons and list.
If y is a list, then we can think of (cons x y) as adding
the new element x to the front of the list y.
(cons 'a '(b c)) = (a b c)
(cons 'a '()) = (a)
(cons '(a) '(b)) = ((a) b)
The list function makes a list out of the elements that follow:
(list 'a 'b 'c 'd) = (a b c d)
(list 'a '(b c)) = (a (b c))
(list 'a '()) = (a ())
(list '(a) '(b)) = ((a) (b))
List Manipulation Functions
append makes a new list consisting of the members of
its argument lists. append takes any number of arguments.
(append '(a) '(b)) = (a b)
(append '(a b) '(c d)) = (a b c d)
(append '(a) '(b) '(c)) = (a b c)
reverse makes a new list that is the reverse of the top level of
the list given as its argument.
(reverse '(a b)) = (b a)
(reverse '((a b)(c d))) = ((c d)(a b))
length returns the number of components in the [top level] of
a list.
(length '(a)) = 1
(length '(a b)) = 2
(length '((a b))) = 1
(length '(+ (* x x) (* 3 x) 2)) = 4
Exercises:
(length 5)
(length '(+ 2 3))
(length (+ 2 3))
In each case, explain the result.
all
if
human
((a) (b c))
(+ (* x x) (* 3 x) 2)
(append '(a b c) '( ))
(list '(a b c) '( ))
(cons '(a b c) '( ))
In each case, explain why you received the result that you obtain.
Boolean Values and Expressions
LISP contains the Boolean values true (t) and false
(NIL). Note that the same symbol is used for both the null list
and for the Boolean value false. Also, note that LISP is reasonably
liberal in its evaluation of expressions, in that anything that is not
explicitly false (NIL) is considered true.
(and t t)
(and nil t)
(and t nil)
(and nil nil)
(and t t t t)
(and t t nil t)
(or t t)
(or nil t)
(or t nil)
(or nil nil)
(or t t t t)
(or nil t nil nil)
(or nil nil nil nil)
(not t)
(not nil)
In each case, explain why LISP produced the result given.
(and 'cat t)
(and nil 'cat)
(and 'cat nil)
(and nil nil)
(and t t 'cat t)
(and t t t 'cat)
(and t t t 'cat 'dog)
(or t 'cat)
(or 'cat t)
(or nil 'cat)
(or 'cat nil)
(or 'cat 'dog)
(or nil nil)
(not 'cat)
(not (not 'cat))
In each case, state why you think LISP produces the result given.
Generalize the results to indicate what values and, or,
and not return when they are applied to non-Boolean values.
(defun not-and (A B)
(not (and A B)))
Explain why this procedure evaluates the logical expression
"Not (A And B))".
Apply this procedure to various values (t, NIL) for A and B. In each case,
be sure you understand why the machine prints the output that results.
Run these procedures with various values (t, NIL) for A, B, and C, and
examine the output. In each case, be sure you can explain the resulting
output. Conditional Statements
As in other languages, LISP allows program execution to depend upon the
value of Boolean expressions. Specifically, LISP contains two constructs
that allow conditional execution: cond and if. As you
will see, the cond statement is more general, and one could
consider an if statement as just a special case of cond.
Cond Statements
LISP's cond statement uses expressions to determine what
action is to be taken. This is illustrated in the following procedure:
(defun type-of-number (A)
(cond ((< A 0) "the number is negative")
((> A 0) "the number is positive")
(t "the number is zero")
)
)
Within this cond expression, (< A 0) is first examined.
If it is true, the clause
"the number is negative" is
evaluated and returned. [Evaluation of the cond expression is
finished.] If, however, the expression (< A 0) is false, then
evaluation proceeds with the next expression (> A 0). Again, if
this expression is true, the following expression
"the number is
positive" is evaluated, and evaluation of cond is completed.
But if this expression also is false, then we move to the next part of
cond. In LISP, the last condition for a cond statement
always should be t. In this way, if we get this far, the action
that follows always will be evaluated.
Fill in the Boolean conditions (indicated as ??? in the file)
to test for the cases specified.
Test your procedure with several examples which cover each case.If Statements
LISP's if expression uses a conditional expression to determine
which of two actions to take. This is illustrated in the following
procedure:
(defun is-negative (A)
(if (< A 0)
"the number is negative"
"the number is nonnegative"
)
)
In executing the if expression, the condition (< A 0)
is evaluated. If this is true, the next clause "the number is
negative" is evaluated and returned. If the condition is not true,
the final clause "the number is nonnegative" is evaluated and
returned. Note that this construction is quite similar to an
if-then-else statement in many languages, except that the
keyword else is implicit, but not actually stated.
(if (???1)
"A is smaller than both B or C." ;;then clause
(;;; the else clause -- A is not smaller
if (???2) ;;second condition within cond
"B is smaller than both A and C."
;;; the second else cause -- B is not smaller either
??? etc.
)
)
Recursion in LISP
longest-on-list that takes as its argument any non-empty list
of character strings and returns whichever element of that list is the
longest:
(longest-on-list '("This" "is" "the" "forest" "primeval")) ===> "primeval"
(longest-on-list '("Wherefore" "art" "thou" "Romeo")) ===> "Wherefore"
(longest-on-list '("To" "be" "or" "not" "to" "be")) ===> "not"
(longest-on-list '("foo")) ===> "foo"
(longest-on-list '("keep" "it" "short" "and" "sweet")) ===> "short"
(longest-on-list '("you" "can" "see" "the" "top")) ===> "you"
length that computes the number of
characters in a string:
(defun longer-string (str1 str2)
(if (< (length str1) (length str2))
str2
str1))
ls is a list that contains only one string, by default it
is the longest string on the list, and we can simply extract it with
car and return it. In any other case --
whenever the list is known to have two or more elements -- we can
at least be confident that the longest string is either the first
one on the list, or the longest string from the rest of the list.
This suggests the following recursive procedure:
(defun longest-on-list (ls)
(if (null (cdr ls))
(car ls)
(longer-string (car ls)
(longest-on-list (cdr ls)))))
Now consider the following procedure to compute the kth power of
n:
longer-string and
longest-on-list procedures into LISP, and confirm that it
correctly finds the longest string in each of the six examples given at the
beginning of this document.("red" "white" "and" "blue"),
and explain how the procedure produces its result.longest-on-list is
applied to an empty list.
(defun power (n k)
(if (= k 1)
n
(* n (power n (- k 1)))))
power and confirm that it
correctly computes the square of 12, the cube of 5, and the sixth power of
10.
power to
compute a negative power of a number? (You can use a "control-c" in LISP
to halt execution, although you may need to be patient to see the effect of
this command.)
countdown that takes any
non-negative integer start as its argument returns a list of
all the positive integers less than or equal to start, in
descending order:
(countdown 5) ===> (5 4 3 2 1) (countdown 1) ===> (1) (countdown 0) ===> ()
replicate that takes two arguments,
size and item, and returns a list of
size elements, each of which is item:
(replicate 6 'foo) ===> (foo foo foo foo foo foo) (replicate 2 NIL) ===> (NIL NIL) (replicate 1 15) ===> (15) (replicate 3 '(alpha beta)) ===> ((alpha beta) (alpha beta) (alpha beta)) (replicate 0 'help) ===> ()
One very common pattern involves constructing a list by applying some given procedure to every element of a given list and collecting the results. For example, the following procedure constructs a list of the squares of the numbers on a given list:
(defun square-each-element (ls)
(if (null ls)
'()
(cons (* (car ls) (car ls))
(square-each-element (cdr ls)))))
Give a similar definition for a procedure double-each-element
that takes a list of numbers and returns a list of their doubles:
(double-each-element '(3 -62 41.4 17/4)) ===> (6 -124 82.8 17/2) (double-each-element '(0)) ===> (0) (double-each-element '()) ===> ()
In some cases, it may be helpful to define two procedures to solve a problem -- one to set up the initial call to the other, supplying additional arguments to assist the recursion. This is illustrated in the following problem and solution:
Problem : Write a procedure called tally-by-parity that
takes any list ls of integers and returns a two-element list
in which the first element is the number of odd integers in ls
and the second is the number of even integers in ls.
(tally-by-parity '(2 3 5 7 11 13)) ===> (5 1) (tally-by-parity '(0 1 2 3 4 5 6)) ===> (3 4) (tally-by-parity '(-8 124 0 124)) ===> (0 4) (tally-by-parity '()) ===> (0 0)Proposed Solution:
(defun tally-by-parity (ls)
(tally-helper ls '(0 0)))
(defun tally-helper (ls ct-pair)
(cond ((null ls) ct-pair)
((oddp (car ls))
(tally-helper (cdr ls)
(list (+ 1 (car ct-pair)) (cadr ct-pair))))
(t (tally-helper (cdr ls)
(list (car ct-pair) (+ 1 (cadr ct-pair)))))))
tally-helper does.
Write a procedure called iota that takes any non-negative
integer upper-bound as argument and returns a list of the
non-negative integers strictly less than upper-bound, in
ascending order:
(iota 6) ===> (0 1 2 3 4 5) (iota 2) ===> (0 1) (iota 1) ===> (0) (iota 0) ===> ()
This document is available on the World Wide Web as
http://www.math.grin.edu/~walker/courses/261/lab-beginning-LISP-2.html