Transcript Lecture 4
The Case primitive: matches the evaluated key
form against the unevaluated keys by using eql
The general format of case is the following:
(case <key form>
(<key 1> <consequent 1-1> ... <consequent 1-n>)
(<key 2> <consequent 2-1> ... <consequent 2-n>)
.....
(<key m> <consequent m-1> ... <consequent m-n>))
Example:
* (setf object 'sphere r 2)
2
* (case object
(circle (* pi r r))
(sphere (* 4 pi r r)))
50.2654824574367
The key may be an atom or a list; in the latter
case the list is searched for the key by means
of the member predicate
Example:
* (case object
((circle wheel) (* pi r r))
((ball sphere) (* 4 pi r r))
(otherwise 0))
; if none of the clauses is
triggered, case
50.2654824574367
; returns NIL, the otherwise clause
(or t)
; can be
used to set the returned value.
The problem reduction technique
Divide the problem into sub-problems each of which can be
handled separately.
Example: the both-ends procedure
(defun both-ends (whole-list)
(case (length whole-list)
(0 ....)
==>
(1 ....)
==>
(2 ....)
==>
(t ....)))
==>
(0 nil)
(1 (cons (first whole-list) whole-list))
(2 whole-list)
(t (cons (first whole-list)
(last whole-list)))))
The procedure abstraction technique: arrange
procedures into “abstraction” layers.
Example: the both-ends procedure
* (defun both-ends (whole-list)
(make-list
(get-first-el whole-list)
(get-last-el whole-list)))
BOTH-ENDS
* (defun make-list (el-1 el-2)
(list el-1 el-2))
MAKE-LIST
* (defun get-first-el (list-1)
(first list-1))
GET-FIRST-EL
* (defun get-last-el (list-1)
(first (last list-1)))
GET-LAST-EL
Defvar and defparameter define global variables
(defvar *global*) declares *global* as a global variable.
(defvar *global* expression) assigns a value to *global*
(defparameter *global* expression) declares *global* as a
global variable, and assings a value to it.
Examples:
* (defvar *var-1*)
* (defparameter *par-1*)
*VAR-1*
ERROR
* (defvar *var-1* 222)
* (defparameter *par-1* 77)
*VAR-1*
77
* *var-1*
* *par-1*
222
77
* (defvar *var-1* 333)
Singly recursive procedures: each recursive call
originates only one new call
Example: compute the “n” power of m.
* (defun exponent (m n)
(if (zerop n)
1
(* m (exponent m (- n 1)))))
EXPONENT
* (exponent 2 3)
8
The behavior of recursive procedures can be observed by means of the
trace primitive. Untrace will undo the effect of trace. These have the
following syntax:
(trace <procedure name>)
(untrace <procedure name>)
* (trace exponent)
(EXPONENT)
* (exponent 2 3)
| 1 Entering: EXPONENT, argument-list: (2 3)
|
2 Entering: EXPONENT, argument-list: (2 2)
|
| 3 Entering: EXPONENT, argument-list: (2 1)
|
|
4 Entering: EXPONENT, argument-list: (2 0)
|
|
4 Exiting: EXPONENT, value 1
|
| 3 Exiting: EXPONENT, value 2
|
2 Exiting: EXPONENT, value 4
| 1 Exiting: EXPONENT, value 8
8
Doubly recursive procedures: each call
originates two recursive calls
Example: compute Fibonacci numbers
* (defun fibonacci (n)
(if (or (= n 0) (= n 1))
1
(+ (fibonacci (- n 1))
(fibonacci (- n 2)))))
FIBONACCI
* (fibonacci 5)
8
* (trace fibonacci)
(FIBONACCI)
* (fibonacci 5)
| 1 Entering: FIBONACCI, argument-list: (5)
|
2 Entering: FIBONACCI, argument-list: (4)
|
| 3 Entering: FIBONACCI, argument-list: (3)
|
|
4 Entering: FIBONACCI, argument-list: (2)
|
|
| 5 Entering: FIBONACCI, argument-list: (1)
|
|
| 5 Exiting: FIBONACCI, value 1
|
|
| 5 Entering: FIBONACCI, argument-list: (0)
|
|
| 5 Exiting: FIBONACCI, value 1
|
|
4 Exiting: FIBONACCI, value 2
|
|
4 Entering: FIBONACCI, argument-list: (1)
|
|
4 Exiting: FIBONACCI, value 1
|
| 3 Exiting: FIBONACCI, value 3
|
| 3 Entering: FIBONACCI, argument-list: (2)
|
|
4 Entering: FIBONACCI, argument-list: (1)
|
|
4 Exiting: FIBONACCI, value 1
|
|
4 Entering: FIBONACCI, argument-list: (0)
|
|
4 Exiting: FIBONACCI, value 1
|
| 3 Exiting: FIBONACCI, value 2
|
2 Exiting: FIBONACCI, value 5
|
2 Entering: FIBONACCI, argument-list: (3)
|
| 3 Entering: FIBONACCI, argument-list: (2)
|
|
4 Entering: FIBONACCI, argument-list: (1)
|
|
4 Exiting: FIBONACCI, value 1
|
|
4 Entering: FIBONACCI, argument-list: (0)
|
|
4 Exiting: FIBONACCI, value 1
|
| 3 Exiting: FIBONACCI, value 2
|
| 3 Entering: FIBONACCI, argument-list: (1)
|
| 3 Exiting: FIBONACCI, value 1
|
2 Exiting: FIBONACCI, value 3
| 1 Exiting: FIBONACCI, value 8
8
Tail recursive procedures: a way to make
recursion efficient
Example: compute the number of elements in a list (version 1)
* (defun count-elements-1 (list-1)
(if (endp list-1)
0
(+ 1 (count-elements-1 (rest list-1)))))
COUNT-ELEMENTS-1
* (setf list-1 '(a b c d))
* (count-elements-1 list-1)
| 1 Entering: COUNT-ELEMENTS-1, argument-list: ((A B C D))
| 2 Entering: COUNT-ELEMENTS-1, argument-list: ((B C D))
| | 3 Entering: COUNT-ELEMENTS-1, argument-list: ((C D))
| | 4 Entering: COUNT-ELEMENTS-1, argument-list: ((D))
| | | 5 Entering: COUNT-ELEMENTS-1, argument-list: (NIL)
| | | 5 Exiting: COUNT-ELEMENTS-1, value 0
| | 4 Exiting: COUNT-ELEMENTS-1, value 1
| | 3 Exiting: COUNT-ELEMENTS-1, value 2
| 2 Exiting: COUNT-ELEMENTS-1, value 3
| 1 Exiting: COUNT-ELEMENTS-1, value 4
4
Tail recursion (cont.)
Example: compute the number of elements in a list (version 2)
* (defun count-elements-2 (list-1)
(count-elements-2-aux list-1 0))
COUNT-ELEMENTS-2
* (defun count-elements-2-aux (list-1 result)
(if (endp list-1)
result
(count-elements-2-aux (rest list-1) (+ 1 result))))
COUNT-ELEMENTS-2-AUX
* (count-elements-2 list-1)
| 1 Entering: COUNT-ELEMENTS-2-AUX, argument-list: ((A B C D) 0)
| 2 Entering: COUNT-ELEMENTS-2-AUX, argument-list: ((B C D) 1)
| | 3 Entering: COUNT-ELEMENTS-2-AUX, argument-list: ((C D) 2)
| | 4 Entering: COUNT-ELEMENTS-2-AUX, argument-list: ((D) 3)
| | | 5 Entering: COUNT-ELEMENTS-2-AUX, argument-list: (NIL 4)
| | | 5 Exiting: COUNT-ELEMENTS-2-AUX, value 4
| | 4 Exiting: COUNT-ELEMENTS-2-AUX, value 4
| | 3 Exiting: COUNT-ELEMENTS-2-AUX, value 4
| 2 Exiting: COUNT-ELEMENTS-2-AUX, value 4
| 1 Exiting: COUNT-ELEMENTS-2-AUX, value 4
4
The Tower of Hanoi example
Version 1: uses a doubly recursive procedure.
* (defun tower-of-hanoi-1 (list-of-disks)
(if (endp list-of-disks)
0
(+ (tower-of-hanoi-1 (rest list-of-disks))
1
(tower-of-hanoi-1 (rest list-of-disks)))))
TOWER-OF-HANOI-1
Version 2: uses a singly recursive procedure.
* (defun tower-of-hanoi-2 (list-of-disks)
(if (endp list-of-disks)
0
(+ 1 (* 2 (tower-of-hanoi-2 (rest list-of-disks))))))
TOWER-OF-HANOI-2
Iteration control structure: the DOTIMES
primitive
Dotimes supports iteration on numbers. It has the following format:
(dotimes (<count parameter> <upper-bound form> <result form>)
<body>)
When dotimes is entered, the upper-bound form is evaluated resulting
in a number, say n. Numbers between 0 and n-1 are assigned to the
count parameter one by one, and for each one of these numbers the
body is evaluated. Finally, the result form is evaluated, and its value is
returned by dotimes (if the result form is missing, dotimes returns NIL).
Example: compute the “n” power of m.
* (defun exponent-2 (m n)
(let ((result 1))
(dotimes (count n result)
(setf result (* m result))
)))
(print result)
EXPONENT-2
* (trace exponent-2)
(EXPONENT-2)
* (exponent-2 3 3)
| 1 Entering: EXPONENT-2, argument-list: (3 3)
| 1 Exiting: EXPONENT-2, value 27
; trace is not very useful here.
; Incorporating print forms to see
27
; intermediate results is better .
Iteration control stucture: the DOLIST primitive
Dolist supports iteration on lists. Its format is the following:
(dolist (<element parameter> <list form> <result form>)
<body>)
When dolist is entered, the list form is evaluated returning a list of elements.
Each one of them is assigned to the element parameter, and the body is
evaluated. Finally, the result form is evaluated, and its value is returned by
dolist (if the result form is missing, dolist returns NIL).
Example: count number of a’s in a list.
* (defun count-a (list-1)
(let ((result 0))
(dolist (element list-1 result)
(when (eql element 'a)
(setf result (+ 1 result))))))
COUNT-A
* (setf list-1 '(a s d f a s d w a a))
(A S D F A S D W A A)
* (count-a list-1)
4
DOTIMES and DOLIST can be terminated by
the (return <expression>) form
Whenever (return <expression>) is encountered, dotimes/dolist
terminates and returns the value of <expression>.
Example: does the list contain at least “n” a’s? If yes, return them as soon
as they are found.
* (defun find-n-a (n list-1)
(let ((result 0) (a-list nil))
(dolist (element list-1 a-list)
(cond ((<= n result) (return a-list))
((eql element 'a) (setf result (+ 1 result))
(setf a-list (cons
element a-list)))))))
FIND-N-A
* (find-n-a 2 list-1)
(A A)
The DO primitive works on both numbers and
lists, and is more general than DOTIMES and
DOLIST
Do has the following format:
(do ((<parameter 1> <initial value 1> <update form 1>)
(<parameter 2> <initial value 2> <update form 2>)
…..
(<parameter n> <initial value n> <update form n>))
(<termination test> <intermediate forms> <result form>)
<body>)
When do is entered, its parameters are set to their initial values in parallel
(in the same way as in the let form). If there are no parameters, an empty
parameter list must be provided. The termination test is evaluated always
before the body is evaluated. If it succeeds, intermediate forms and the
result form are evaluated; the value of the result form is returned by do (if
no result form is given, do returns NIL).
Example: compute “n” power of m
Version 1:
Version 2:
* (defun exponent-3 (m n)
(do ((result 1)
(exponent n))
((zerop exponent) result)
(setf result (* m result))
(setf exponent (- exponent 1))))
* (defun exponent-4 (m n)
(do ((result 1)
(exponent n))
()
(when (zerop exponent) (return result))
(setf result (* m result))
(setf exponent (- exponent 1))))
EXPONENT-3
* (exponent-3 3 3)
27
EXPONENT-4
* (exponent-4 3 3)
27
DO* assigns initial values to parameters
sequentially (like let*)
Example which does not work
DO* solves the problem.
* (defun exponent-5 (m n)
(do ((result m (* m result))
(exponent n (- exponent 1))
(counter (- exponent 1) (- exponent 1)))
((zerop counter) result)))
* (defun exponent-6 (m n)
(do* ((result m (* m result))
(exponent n (- exponent 1))
(counter (- exponent 1) (- exponent 1)))
((zerop counter) result)))
EXPONENT-6
A better solution:
EXPONENT-5
* (exponent-5 3 3)
*** Debugger warning: leftover specials ***
>>> Error: {Determining function in error.}
>>> Error:Unbound variable: EXPONENT
(#<COMPILED-FUNCTION 3:E196> ...)
* (defun exponent-7 (m n)
(do* ((result 1 (* m result))
(exponent n (- exponent 1))
(counter exponent (- counter 1)))
((zerop counter) result)))
EXPONENT-7
The LOOP primitive implements infinite loop if
(return <expression>) is not encountered
Loop has the following format:
(loop <body>)
Example: count the number of elements in a list.
* (setf list-1 '(a b c d e))
(A B C D E)
* (setf count-elements 0)
0
* (loop (when (endp list-1) (return count-elements))
(setf count-elements (+ 1 count-elements))
(setf list-1 (rest list-1)))
5
The PROG1 and PROGN primitives allow
several forms to be viewed as a group in which
forms are evaluated sequentially.
Prog1 has the following form:
(prog1 <form 1> <form 2> … <form n>).
All forms are evaluated, and prog1 returns the value of the first form.
Progn has the following form:
(progn <form 1> <form 2> … <form n>).
All forms are evaluated, and progn returns the value of the last form.
Example:
* (prog1 (eql 'a 'a) (setf a 5))
T
* (progn (eql 'a 'a) (setf a 5))
5
The FORMAT primitive: produces more elegant
printing than PRINT
Example:
* (format t "Hi there!")Hi there!
that the output should be
; the t argument says
; printed at the terminal. The returned value is
NIL
; NIL
To print a string on a new line, the ~% directive should be placed at the
beginning of the string:
* (format t "~%Hi there!")
Hi there!
NIL
The TERPRI primitive also moves the cursor to a new line. For example:
* (prog1 (terpri) (format t "Hi"))
Hi
NIL
The ~& directive tells format to print on a new line, if it is not already on
a new line, while the ~a directive tells format to insert the value of an
More examples
* (progn (format t "~%Student name: ~a" name)
(format t "~&Major: ~a" major))
Student name: ANNA
Major: CS
NIL
* (format t "~%Student name: ~10a Major: ~10a" name
major)
Student name: ANNA
Major: CS
NIL
* (format t "~%Student name: ~20a Major: ~10a" name
major)
Student name: ANNA
Major: CS
NIL
The Read primitive reads a value from the
keyboard
Examples:
* (read)hello
input is typed immediately after read.
HELLO
* (setf greetings (read))hi
assigned to a variable.
HI
* greetings
HI
* (setf greetings (read))(hi there!)
more
(HI THERE!)
symbol it must be enclosed in a list.
* greetings
(HI THERE!)
; the
; the input can be
; if the input contains
; than one
Print and read used in combination allow for a
more informative dialog
Example:
* (prog1 (print '(Please enter your name))
(setf name (read))
(print (append '(hi) (list name) '(how are you
today?))))
(PLEASE ENTER YOUR NAME) neli
(HI NELI HOW ARE YOU TODAY?)
(PLEASE ENTER YOUR NAME)
* name
NELI
Do not use “:” in the print argument; for example, name: will be understood
as a reference to a package which does not exist and will result in an error.
Read, Terpri and Format in combination can
also be used for a more informative dialog
Example:
* (prog1 (terpri)
(format t "Please enter your name : ")
(setf name (read)) (terpri)
(format t "Hi ~a how are you today?" name))
Please enter your name : neli
Hi NELI how are you today?
NIL
Notice the space between “name” and “:” in the format statement.