Some midterm stuff
Many of you did poorly on the midterm problem that asked you to write
MY-SUBSTITUTE applicately. Here's a solution and explanation:
(defun my-substitute (new old list)
(mapcar #'(lambda (x) (if (eql x old) new x)) list))
Recall that mapcar takes a function and applies it to each element in a
list, and it returns a list of those results. For a simple example,
(mapcar #'(lambda (x) (+ x 1)) '(1 2 5 6)) --> (2 3 6 7)
shows how to use MAPCAR to add 1 to all the elements of a list.
Pictorially, you can think of mapcar working like this:
(mapcar f list)
list = ( 1 2 5 6 )
| | | |
| | | |
v v v v
f(x) f(x) f(x) f(x)
| | | |
| | | |
v v v v
result = ( 2 3 6 7 )
In the case of MY-SUBSTITUTE, given that it works like
(my-substitute 'f 'a '(a b a c)) --> (F B F C)
the question simply becomes "what function do we need to push the list
down through to get the desired output?"
list = ( A B A C )
| | | |
v v v v
What function needs to go here?
| | | |
v v v v
result = ( F B F C )
The solution, again, is:
(defun my-substitute (new old list)
(mapcar #'(lambda (x) (if (eql x old) new x)) list))
The lambda function simply tests to see if an element should be
substituted--if so, it returns the new element, else the original
element unchanged. Recall that these two
(if x y z) (cond (x y)
(T z))
are equivalent.
Iteration
So far, we've seen two different ways of controlling program
flow. The first was recursion, and the second was what we
called applicative programming. They're both very
functional. But now that you know about variables and
assignment, you're finally ready to learn about iteration.
Common LISP provides you with two very simple iterative
forms. The first is called "dotimes" and looks like this:
(dotimes ([variable] [limit-expr] [result-expr])
:
:
[body of code]
:
:
)
[variable] is a symbol. You specify the name, and LISP
initializes the variable to 0. It is not evaluated.
[limit-expr] is an expression which is evaluated; LISP
expects a number to be returned here.
[result-expr] is an optional argument. When the iteration is
finished, this expression is evaluated and the result is
returned by "dotimes". If no expression is found here,
"dotimes" returns nil when it is done.
This algorithm describes the operation of "dotimes":
1. [variable] := 0
2. if [variable] = [limit-expr]
then return [result-expr] (or nil if no [result-expr])
3. execute body
4. [variable] := [variable] + 1
5. go to 2
See, it's very simple. Don't you wish I had let you use this
earlier? I don't. Anyway, here's an example of using
"dotimes" in, what else, a factorial function:
(defun factorial (number)
(let ((result 1))
(dotimes (counter number result)
(setf result (* result (+ 1 counter))))))
The second simple iterative form is "dolist", which looks
like this:
(dolist ([variable] [list-expr] [result-expr])
:
:
[body of code]
:
:
)
[variable] is a symbol. You specify the name, and LISP binds
this symbol to successive elements of the list given in
[list-expr]. [variable] is not evaluated.
[list-expr] is an expression which is evaluated; LISP expects
a list to be returned here.
[result-expr] is an optional argument. When the iteration is
finished, this expression is evaluated and the result is
returned by "dolist". If no expression is found here,
"dolist" returns nil when it is done.
This algorithm describes the operation of "dolist":
1. if there's no next element of [list-expr]
then return [result-expr] (or nil if no [result-expr])
2. [variable] := next element of [list-expr]
3. execute body
4. go to 1
Note that [list-expr] isn't changed while "dolist" is
running---it doesn't "shrink" by one element each time the
body of code is executed. Here's an example of "dolist" in
"my-remove":
(defun my-remove (item r-list)
(let ((result nil))
(dolist (element r-list result)
(cond ((not (eql element item))
(setf result (append result (list element))))))))
There's another iterative form which is not so simple, at
least in my opinion. It's just called "do", and it looks like this:
(do (([var-1] [init-expr-1] [update-expr-1])
([var-2] [init-expr-2] [update-expr-2])
:
:
([var-n] [init-expr-n] [update-expr-n]))
([test-expr] [action-expr-1] ... [action-expr-m])
:
[body of code]
:
)
Here's how "do" works:
1. each [var-i] is bound to its corresponding [init-expr-i]
(in "parallel", just like with "let"---that is, there's
no guarantee of the order the bindings are completed, so
don't encode dependencies of any kind in this part of the
"do" form).
2. [test-expr] is evaluated. If the result is non-nil, then
the [action-expr-1] through [action-expr-m] are evaluated
in left-to-right fashion. The "do" form returns the
value of [action-expr-m].
3. if evaluating [test-expr] returns nil, then execute the
body of code.
4. when the body of code has been executed, update each
[var-i] by binding it to the value obtained by evaluating
the corresponding [update-expr-i]. The [update-expr-i]
are optional, so if it's not there, then the various
[var-i] are never changed. Also, these bindings are done
automatically by "do", so there's no need to include
your own "setf" or "setq".
5. go to 2
If you use
(return)
or
(return [expr])
in the body of a "do" or "dotimes" or "dolist", the iterative
form will be exited immediately, and will return nil or
[expr] accordingly.
Weird science
With this powerful "do" form, you can write functions where
all the work is done in the "preamble":
(defun factorial (n)
(do ((count n (- count 1))
(result 1 (* result count)))
((= 0 count) result)
;; no body!
))
Some authors of LISP texts (who shall remain nameless) think
that this is elegant programming style. Frankly, it makes my
head hurt. Use it if you want to, but please don't ask me
to debug it.
Other forms of iteration
Another iteration mechanism is the "loop" macro (we'll see
more about macros soon). It has been added to the Common
LISP standard as described in the second edition of Steele,
but it isn't in the first edition. Some LISP folks complain
that the loop macro isn't defined all that well in the
second edition, which leads to some ambiguities. Consequently,
we don't cover it in this class, but feel free to look it up.
And while you're thinking about things iterative, grab
a LISP book and look up the granddaddy of all LISP
iterative forms: "prog". Then once you've read about it,
remember to try to avoid its use. The inclusion of the
"goto" in prog means you want to try to exclude it from
your code in most cases.
Lecture notes by Kurt Eiselt, 1998.
Minor changes / additions by Brian McNamara, 1998.
Last updated on
Thu Aug 13 02:21:37 EDT 1998
by Brian McNamara