在Lisp中限制cons调用的同时将列表中的每个项加倍

在Lisp中限制cons调用的同时将列表中的每个项加倍,lisp,common-lisp,Lisp,Common Lisp,我有一个函数,可以将列表中的每个项目加倍: (defun double (L) (cond ((atom L) nil) (t (cons (car L) (cons (car L ) (double (cdr L))))) ) ) (double '(a b c )) => (a a b b c c) 如果将函数调用的次数除以2,如何得到相同的结果?(即,在上一个示例中,它调用了cons6次。我怎么能只调用3次?) 谢谢 编辑2,在jkiiski的评论之

我有一个函数,可以将列表中的每个项目加倍:

(defun double (L)
   (cond
      ((atom L) nil)
      (t (cons (car L) (cons (car L ) (double (cdr L))))) ) )

(double '(a b c )) => (a a b b c c)
如果将函数调用的次数除以2,如何得到相同的结果?(即,在上一个示例中,它调用了
cons
6次。我怎么能只调用3次?)

谢谢

编辑2,在jkiiski的评论之后,似乎现在可以工作了:

(defun double2 (L)
   (cond
      ((atom L) nil)
      (t (setf
             (cdr L)
             (cons (car L ) (double2 (cdr L))))
         L) ) )


(double2 (list 'a 'b 'c)) => (a a b b c c) 
(defun double2 (L)
   (cond
      ((atom L) nil)
      (t (setf
             (cdr L)
             (cons (car L ) (double2 (cdr L))))
         L) ) )

(double2 (list 'a 'b 'c)) => (a a b b c c) 

这里是另一种方法,没有递归。请注意,这个答案假设您正在做家庭作业,并试图找到一种避免创建新列表的方法,而最简单的解决方案就是这样做的。实际上,您只需在
循环中使用
收集
两次,就像Sylwester演示的那样。在这里,原始输入列表被破坏性地修改

复印件清单 假设您的原始
列表是
(1 2 3)
。而不是考虑 元素,您可以调用
(复制列表)
,它执行 所需的考虑量。这就是你需要考虑的所有记忆 分配。然后,您“只”需要将所有cons单元格交错到 获得所需的重复

保留给定的
列表
变量,并定义两个 迭代两个列表:

current = (1 2 3) ;; original
  fresh = (1 2 3) ;; copy
重新排列单元格 从图形上看,您希望将CDR更改为将现有cons单元“线程化”在一起。首先,两个列表如下所示:

  current   ( 1 . x-)-->( 2 . x-)-->...

  fresh     ( 1 . x-)-->( 2 . x-)-->...
但是你想要:

  current   ( 1 . x )   ( 2 . x )
                  |     ^     |
                  V     |     V
  fresh           ( 1 . x )   ( 2 . ...)
更正式地说,在每个步骤开始时,当列表不为空时,可以按如下方式分解上述变量:

current = (chead . ctail)
  fresh = (fhead . ftail)
current = (chead . (fhead . ctail))
  fresh = ftail
您希望将
当前的
尾部指向
新的
cons单元格,并将
新的
尾部指向
ctail
。 完成单元格交错后,应按如下方式绑定变量:

current = (chead . ctail)
  fresh = (fhead . ftail)
current = (chead . (fhead . ctail))
  fresh = ftail
然后,您可以在
current
中下降两次,以便最终:

current = ctail
  fresh = ftail
从这里开始,您可以继续处理这两个列表的其余部分。注意
列表
仍然包含作为 输入

代码 我正在使用连接cons单元:

(rotatef (cdr fresh) (cdr current) fresh)
发生的情况是,
fresh
的值被放置在
(cdr当前)
中,其先前的值本身被放置在原始的
(cdr fresh)
中 其值最终成为绑定到
fresh
的新值

例子 以下是跟踪输出的示例:

CL-USER> (double-loop (list 0 1 2 3))

((0 1 2 3) (0 1 2 3) (0 1 2 3)) 
((0 0 1 2 3) (1 2 3) (1 2 3)) 
((0 0 1 1 2 3) (2 3) (2 3)) 
((0 0 1 1 2 2 3) (3) (3))

=> (0 0 1 1 2 2 3 3)

在jkiiski的评论之后,它现在似乎起作用了:

(defun double2 (L)
   (cond
      ((atom L) nil)
      (t (setf
             (cdr L)
             (cons (car L ) (double2 (cdr L))))
         L) ) )


(double2 (list 'a 'b 'c)) => (a a b b c c) 
(defun double2 (L)
   (cond
      ((atom L) nil)
      (t (setf
             (cdr L)
             (cons (car L ) (double2 (cdr L))))
         L) ) )

(double2 (list 'a 'b 'c)) => (a a b b c c) 

CONS
的单个调用构造一个CONS单元。输入有三个cons单元格,因此很明显,在只创建三个新单元格的同时获得六个单元格的结果的唯一方法是重用旧的三个单元格。但是请注意,在您的示例中,您使用带引号的列表调用函数。带引号的列表是不应该变异的文本数据。您必须使用新构造的列表调用函数--
(double(list'a'b'c))
,如果它改变了输入。@jkiiski:谢谢。那么,我如何“重用”已经存在的囚犯呢?使用rplacd?这样做不会使代码运行得更快,但会限制您使用文字,否则会改变输入,因此您需要小心,不要因为这种过早的优化而引入错误。我宁愿做
(loop:for e:in l:collect e:and:collect e)
,这会减少6倍,但不会增加堆栈。@coredump:是的,这是一些人喜欢的。它返回被修改的cons单元格,因此您不必像使用
(RPLACD L…
那样返回
L
,它只会工作。最好不要在它们的行中单独使用悬空括号。这会调用
cons
6次,就在引擎盖下。@Sylwester(1)这可能是一个家庭作业,至少学习一次如何操作cons单元格,或者当可以使用ROTATEF时,这并不是无用的。(2)当你说6时,你可能会计算原始列表的创建,而这不是问题所在(如果我不打印中间结果,而是收集它们,我最终获得
(#1=(1.#2=(1.#3=(2.#4=(2.#5=(3.##################6########1#####3####4############尽管如此,我100%同意这种代码不应该被鼓励这样做。我将对此做一点评论。谢谢。我知道了大概的想法,尽管我没有掌握代码的所有细节。我将仔细查看并使用它。您可能需要先评估
(setf*print circle*t)
为避免打印循环结构时出现无限循环(如果将cons单元格的cdr设置为(in)直接引用该单元格的cdr,则可能会发生这种情况),请参阅