Lisp 更改列表的第n个元素

Lisp 更改列表的第n个元素,lisp,common-lisp,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))

我想更改列表的第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))
做这件事最好的方法是什么?

怎么样

(defun set-nth4 (list n val)
  (loop for i from 0 for j in list collect (if (= i n) val j)))
也许我们应该注意到与替换的相似性,并遵循其惯例:

(defun substitute-nth (val n list)
  (loop for i from 0 for j in list collect (if (= i n) val j)))
顺便说一句,关于
set-nth3
,有一个函数,正好适用于这种情况:

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

这取决于你所说的“优雅”是什么意思,但是

(defun set-nth (list n val)
  (if (> n 0)
      (cons (car list)
            (set-nth (cdr list) (1- n) val))
      (cons val (cdr list))))
如果您在容易理解递归定义方面存在问题,那么对您来说,N-2的一个细微变化(如Terje Norderhaug所建议的)应该更“不言而喻”:

我能看到的这个版本唯一的效率缺陷是遍历第n个元素要三次,而不是递归版本中的一次(但不是尾部递归)。

这样如何:

(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)
    ))
(定义设置n(列表n值)
(环路
对于列表上的单元格
我从0开始
当(
从负面来看,它更像是Algol而不是Lisp。但从好的方面来说:

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

  • 它根本不遍历输入列表的尾部

  • 构建输出列表时无需再次遍历它

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


考虑使用
nthcdr
重用原始列表的尾部。此外,由于
subseq
总是创建一个新序列,您可以使用破坏性的
ncoc
而不是
串联
,以减少考虑和提高性能。就命名而言,我希望任何名为SET-。。。是破坏性的,而不是复制。我可能会把它称为COPY-WITH-SHADOWED-NTH或类似的。此外,由于需要进行尾部共享,因此很难找到正确的、描述性的、简短的名称。您可以在第三个示例中使用
(经常使用t)
,我的意思是,通过查看代码,代码的目的应该是不言而喻的。到目前为止,只有set-nth1通过了此测试。而且,它应该是高效的,因此没有不必要的考虑。
set-nth-2bis
应该是不言而喻的,并且尽量减少考虑。使用
cons
而不是
(cons-val(nthcdr(1+n)list))
中的
list可能会稍微提高可读性,但这是一个品味问题。@Terje:从(list…)改为(cons…)谢谢你,也谢谢你告诉我关于“持续”的事情——我经常需要它!嗯……所以,可能LISP在性能上不如第n个元素好,我们需要遍历n个值,在系统语言中,我们只需向指针添加
(n*sizeof(element))
。这是任何大小的元素都可以保存的代价。必须“通过n个值”是选择链表作为数据结构的结果,而不是因为选择编程语言。如果随机访问很普遍,那么选择向量或数组,CommonLisp当然提供了这些。(如果你正在阅读垃圾类的材料,比如“LISP中唯一的数据结构是list”,扔掉它。它已经过时了。)
(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)
    ))