更改列表的第n个元素

时间:2011-05-19 13:20:59

标签: lisp common-lisp

我想更改列表的第n个元素并返回一个新列表。

我想到了三个相当不优雅的解决方案:

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

这样做的最佳方式是什么?

3 个答案:

答案 0 :(得分:5)

怎么样

(defun set-nth4 (list n val)
  (loop for i from 0 for j in list collect (if (= i n) val j)))

也许我们应该注意与substitute的相似性并遵循其惯例:

(defun substitute-nth (val n list)
  (loop for i from 0 for j in list collect (if (= i n) val j)))

BTW,关于set-nth3,有一个函数constantly,完全符合以下情况:

(defun set-nth3 (list n value)
  (substitute value nil list :test (constantly t) :start n :count 1))

编辑:

另一种可能性:

(defun set-nth5 (list n value)
  (fill (copy-seq list) value :start n :end (1+ n)))

答案 1 :(得分:3)

这取决于你对“优雅”的意思,但是......那就是......

(defun set-nth (list n val)
  (if (> n 0)
      (cons (car list)
            (set-nth (cdr list) (1- n) val))
      (cons val (cdr list))))

如果您在轻松理解递归定义方面存在问题,那么nth-2的微小变化(如Terje Norderhaug所建议的那样)对您来说应该更加“不言而喻”:

(defun set-nth-2bis (list n val)
  (nconc (subseq list 0 n)
         (cons val (nthcdr (1+ n) list))))

我可以看到这个版本的唯一效率缺点是遍历第n个元素的遍历完成了三次而不是递归版本中的一个(但不是尾递归)。

答案 2 :(得分:1)

这个怎么样:

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

在负面,它看起来更像是Algol而不是Lisp。但从好的方面来说:

  • 它只遍历输入列表的前导部分

  • 它不会遍历所有

  • 的输入列表的尾部
  • 构建输出列表而无需再次遍历

  • 结果与原始列表共享相同的尾随缺点单元格(如果不需要,请将nconc更改为append