2

I am wondering what is the most efficient (algrotihmically) and readable way of doing something like this in Common Lisp:

(setq result-list (append result-list small-list))

That is, is there something like (append-destructively result-list small-list) that would make sure that result-list contains the concatenated list?

I am doing this to improve readability, but is it a good idea when it comes to (functional) programming practices?

MadPhysicist
  • 5,401
  • 11
  • 42
  • 107
  • 2
    Alexandria has modify macros `APPENDF` and `NCONCF`. You could also define them yourself easily with `DEFINE-MODIFY-MACRO`. – jkiiski Jun 13 '18 at 16:04
  • I have never heard of `DEFINE-MODIFY-MACRO`. Is that Common Lisp at large or some implementation-specific thing? – MadPhysicist Jun 13 '18 at 16:05
  • 3
    It's Common Lisp: [CLHS](http://www.lispworks.com/documentation/HyperSpec/Body/m_defi_2.htm) – jkiiski Jun 13 '18 at 16:08

3 Answers3

5

If you have to do it over and over again on the same list, tail-wagging might be the best solution (which I learned from Edi Weitz's book):

(defparameter *l* (list 'a 'b 'c 'd 'e 'f))

give the last cons-cell of *l* a name

(defparameter *tail* (last *l*))

now, (cdr *tail*) is the last element of the list: '(). let's say you want to add (list 'g 'h 'i) to its end. assign (setf) it to (cdr *tail*) which is the last cons-cell: '() of the list *l*, and reassign (setf) the new last cons element to tail.

(setf (cdr *tail*) (list 'g 'h 'i)
      *tail*       (last *tail*))

now, *l* is mutated to contain the second list. and *tail* names the last cons cell of this new list.

*l*
;; (a b c d e f g h i)
*tail*
;; (i)

The next time, when *tail* has to be extended by a list, you don't have to traverse all the list again, but can just assign to cdr of *tail* the to-be-appended-list and then the original list will be modified.

by the way, I stumbled over tail-wagging when contemplating over tailp

Gwang-Jin Kim
  • 9,303
  • 17
  • 30
4

The destructive append is called nconc.

Note, however, that you still need to do

(setf result-list (nconc result-list small-list))

because if result-list is nil, then (nconc result-list small-list) is small-list and result-list is unmodified.

Another issue to be aware of is that both nconc and append are linear in result-list length, so this is not a very good way to store a lot of data.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
sds
  • 58,617
  • 29
  • 161
  • 278
3

Looking at your question, I’m going to guess at what you want and make some hints for if you want to repeatedly extend a list but don’t want to have to scan it again and again.

If you are in a loop you can use collect, append, or nconc

(defun nconcat (lists)
  (loop for list in lists nconc list))

If you want to do this in a more general setting the idea is to keep track of the end of the list so that appending needs only to scan the short list. Example:

(defun nconcat (lists)
  (let* ((result-ref (list nil))
         (end result-ref))
    (mapc
      (lambda (list)
        (setf (cdr end) list
              end (last end)))
      lists)
    (cdr result-ref)))
Dan Robertson
  • 4,315
  • 12
  • 17