Good to see you coding in some kind of Lisp, but again, you're using a simple
O(N * N) algorithm, which allows the code to be glib. My code is more
complicated, but it gets the time down to O(N). That starts to get important
if you have only just several thousand items, and the operation is frequently
executed. Also, I had the impression that it was a homework problem. Glib
solutions to homework problems go straight to the prof for an A+ grade.
But anyway, here is how the above looks like in Common Lisp: it's not very
different from what you have there. Why don't you just use the real thing!
(defun coalesce (lists)
(let (accum)
(dolist (x lists (nreverse accum))
(let ((i (position x accum :test (lambda (a b) (intersection a b)))))
(if i
(setf (nth i accum) (union (nth i accum) x))
(push x accum))))))
At least we can compile this to get some speedup. NewLisp is interpreted only,
and its semantics are screwed up to make sure it stays that way forever.
Furthermore, NewLisp's "ORO" memory management means that it's copying linked
lists around instead of efficiently passing references.
How about we drill down on one particular suckiness in the CL version:
;; double evaluation of (nth i accum)
(setf (nth i accum) (union (nth i accum) x))
Lisp has a nice feature which lets us write it like this:
(unionf (nth i accum) x)
such that the (nth i accum) form is evaluated only once. The GET-SETF-EXPANSION
function gives us access to Lisp's setf-place analyzer that is uses to compile
the operators that modify place forms.
The naive definition of unionf still evaluates the place twice, giving us only
the syntactic convenience, with a hidden programmer pitfall.
(defmacro unionf (set-place set)
(setf ,set-place (union ,set-place ,set)))
The "pro" version is harder to write, but may be worth it---not only
for efficiency (which we may not get, depending on how good the setf expansion
material is) but for cleaner semantics. Forms can contain side effects that
get done multiple times if there are multiple evaluations, which is a
programmer pitfall. And thus:
(defmacro unionf (set-place set &environment env)
(multiple-value-bind (temp-vars vals new-vars store-form load-form)
(get-setf-expansion set-place env)
(when (rest new-vars)
(error "unionf: not usable with multiple value place ~s" set-place))
`(let (,@(mapcar #'list temp-vars vals)
(,(first new-vars) ,load-form))
(prog1
(setf ,(first new-vars) (union ,(first new-vars) ,set))
,store-form))))
A simple instance:
(macroexpand '(unionf a b))
-> (LET ((#:NEW-12734 A))
(PROG1 (SETF #:NEW-12734 (UNION #:NEW-12734 B)) (SETQ A #:NEW-12734)))
Very good; so how about an (nth ...) place:
(macroexpand '(unionf (nth x y) x))
-> (LET ((#:TEMP-12737 X) (#:TEMP-12736 Y)
(#:NEW-12735 (NTH #:TEMP-12737 #:TEMP-12736)))
(PROG1 (SETF #:NEW-12735 (UNION #:NEW-12735 X))
(SYSTEM::%SETNTH #:TEMP-12737 #:TEMP-12736 #:NEW-12735)))
(This could be better: we have the single evaluation of the place form,
but it still marches down the list twice. The above is from CLISP; clearly
they didn't see it fit to bother with the best possible setf expansion
for NTH.)
In NewLisp you could get single evaluation by using a fexpr. But fexprs evaluate
the source code at run time. With applications relying on that, good luck
making a compiled NewLisp one day.