I've started studying Lisp 2 days ago and I'm reading Paul Graham's ANSI Common List, which exposes the language structure in a very interesting way. It's not too much theoretical for beginners, and not too shallow (as Sierra-Bate's Head First Java, which personally I hate). After a brief general language introduction, he starts talking bout lists and raise an example of simple list compression. Basically, let el be an element which is repeated n times. You replace all of them by a single (n el) list. To do this he gave an code implementation, but I tried to do my own, which apparently is working. I'd like if possible, that someone analyses my code and show me the critical points of its implementation, which I'm sure there is a lot, since it's my first contact with Lisp. Thank u all!
(defun compress (x)
"compress a list replacing repeated sequential values for (<n> <value>)"
(let ((lst '()) (fst t) (lt nil) (n 0))
(dolist (el x)
(if fst
(progn
(setq fst nil)
(setq lt el)
(setq n 1))
(progn
(if (equal el lt)
(setq n (+ n 1))
(progn
(setq lst (cons (if (= n 1)
lt
(list n lt))
lst))
(setq lt el)
(setq n 1)
)))))
(setq lst (cons (if (and (not (= n 0)) (= n 1))
lt
(list n lt))
lst))
(reverse lst)))
The algorithm seems quite reasonable. I only find the fst
variable superfluous. You use it only when entering the loop, so you could just as well move the first bunch of assignments to the preamble and iterate over the rest elements of the list.
Now the question is how to express the algorithm in Lisp.
The most significant point is that you can use nreverse
instead of reverse
. nreverse
is more efficient, but it destroys the structure of its argument. Generally, you don't want that because of so-called shared structure: a cons cell can be a part of a few lists, so modifying a cons sell you can bring about unexpected changes in apparently unrelated lists. (PCL treats this issue very well.) However, in your function you construct lst
from scratch, manually pushing new elements onto it, so there is no way it could share structure with other lists. So you can safely dispense with the structure. This is the common push/nreverse
idiom. reverse
just conses up a new list, and lst
becomes garbage.
To make the code more idiomatic, you could use standard shortcuts like incf
for +=
and push
for cons=
. Nowadays the universal setf
seems to be more popular then setq
. Personally, I prefer using cond
where a progn
would otherwise appear. So you could end up with something like this:
(defun compress (x)
"compress a list replacing repeated sequential values for (<n> <value>)"
(if x
(let ((lst '())
(lt (first x))
(n 1))
(dolist (el (rest x))
(cond ((equal el lt) (incf n))
(t (push (if (= n 1)
lt
(list n lt))
lst)
(setf lt el)
(setf n 1))))
(push (if (= n 1)
lt
(list n lt))
lst)
(nreverse lst))
nil))