Changing the nth element of a list

2020-08-10 19:55发布

问题:

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?

回答1:

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)))


回答2:

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).



回答3:

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)