I want to change the nth element of a list and return a new list.
I've thought of three rather inelegant solutions:
(defun set-nth1 (list n value)
(let ((list2 (copy-seq list)))
(setf (elt list2 n) value)
list2))
(defun set-nth2 (list n value)
(concatenate 'list (subseq list 0 n) (list value) (subseq list (1+ n))))
(defun set-nth3 (list n value)
(substitute value nil list
:test #'(lambda (a b) (declare (ignore a b)) t)
:start n
:count 1))
What is the best way of doing this?
How about
(defun set-nth4 (list n val)
(loop for i from 0 for j in list collect (if (= i n) val j)))
Perhaps we should note the similarity to substitute
and follow its convention:
(defun substitute-nth (val n list)
(loop for i from 0 for j in list collect (if (= i n) val j)))
BTW, regarding set-nth3
, there is a function, constantly, exactly for situation like this:
(defun set-nth3 (list n value)
(substitute value nil list :test (constantly t) :start n :count 1))
Edit:
Another possibility:
(defun set-nth5 (list n value)
(fill (copy-seq list) value :start n :end (1+ n)))
It depends on what you mean for "elegance", but what about...
(defun set-nth (list n val)
(if (> n 0)
(cons (car list)
(set-nth (cdr list) (1- n) val))
(cons val (cdr list))))
If you have problems with easily understanding recursive definitions then a slight variation of nth-2 (as suggested by Terje Norderhaug) should be more "self-evident" for you:
(defun set-nth-2bis (list n val)
(nconc (subseq list 0 n)
(cons val (nthcdr (1+ n) list))))
The only efficiency drawback I can see of this version is that traversal up to nth element is done three times instead of one in the recursive version (that's however not tail-recursive).
How about this:
(defun set-nth (list n value)
(loop
for cell on list
for i from 0
when (< i n) collect (car cell)
else collect value
and nconc (rest cell)
and do (loop-finish)
))
On the minus side, it looks more like Algol than Lisp. But on the plus side:
it traverses the leading portion of the input list only once
it does not traverse the trailing portion of the input list at all
the output list is constructed without having to traverse it again
the result shares the same trailing cons cells as the original list (if this is not desired, change the nconc
to append
)