lisp - 使用 mapcan 卡住创建列表的重复?

标签 lisp common-lisp

我有两个列表:(1 2 3)(a b) 我需要创建这样的列表 (1 2 3 1 2 3) 。结果是第一个列表的串联次数与第二个列表中的元素数一样多。我应该使用一些功能(maplist/mapcar/mapcon 等)。这正是我所需要的,尽管我需要将第一个列表作为参数传递:

(mapcan #'(lambda (x) (list 1 2 3)) (list 'a 'b))
;=> (1 2 3 1 2 3)

但是,当我尝试将其抽象为一个函数时,Allegro 卡住了:

(defun foo (a b)
  (mapcan #'(lambda (x) a) b))

(foo (list 1 2 3) (list 'a 'b))
; <freeze>

为什么这个定义不起作用?

最佳答案

已经有一个公认的答案,但我认为需要对原始代码中出现的问题进行更多解释。 mapcan 将函数应用于列表的每个元素以生成一堆列表,这些列表被破坏性地连接在一起。如果你破坏性地将一个列表与其自身连接起来,你会得到一个循环列表。例如,

(let ((x (list 1 2 3)))
  (nconc x x))
;=> (1 2 3 1 2 3 1 2 3 ...)

现在,如果您的连接数多于一个,则无法完成,因为将某些内容连接到列表末尾需要走到列表末尾。所以

(let ((x (list 1 2 3)))
  (nconc (nconc x x) x))
;        -----------      (a)
; ---------------------   (b)

(a) 终止,并返回列表 (1 2 3 1 2 3 1 2 3 ...),但 (b) 无法终止,因为我们无法到达(1 2 3 1 2 3 ...) 的结尾,以便将内容添加到结尾。

现在剩下的问题是为什么

(defun foo (a b)
  (mapcan #'(lambda (x) a) b))

(foo (list 1 2 3) '(a b))

导致卡住。由于 (a b) 中只有两个元素,这相当于:

(let ((x (list 1 2 3)))
  (nconc x x))

那应该终止并返回一个无限列表 (1 2 3 1 2 3 1 2 3 ...)。事实上,确实如此。问题是 打印 REPL 中的列表将会挂起。例如,在 SBCL 中:

CL-USER> (let ((x (list 1 2 3)))
           (nconc x x))
; <I manually stopped this, because it hung.

CL-USER> (let ((x (list 1 2 3)))
           (nconc x x)            ; terminates
           nil)                   ; return nil, which is easy to print
NIL

如果将 *print-circle* 设置为 true,您可以看到第一种形式的结果:

CL-USER> (setf *print-circle* t)
T
CL-USER> (let ((x (list 1 2 3)))
           (nconc x x))
#1=(1 2 3 . #1#)                  ; special notation for reading and
                                  ; writing circular structures

调整代码以消除问题行为的最简单方法(即,最少的更改)是在 lambda 函数中使用 copy-list:

(defun foo (a b)
  (mapcan #'(lambda (x)
              (copy-list a))
          b))

这也优于 (reduce 'append (mapcar ...) :from-end t) 解决方案,因为它不一定分配结果的中间列表。

关于lisp - 使用 mapcan 卡住创建列表的重复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23293701/

相关文章:

emacs - 无法将目录及其所有子目录添加到 Emacs 中的加载路径

if-statement - `if` 的替代实现 - 难以理解的行为

lisp - CLOS:使用一个槽值来初始化另一个槽

eclipse - 让 CUSP 在 Eclipse 中工作

lisp - 处理槽和字符串列表中的值

lisp - 在 Common Lisp 中交换列表元素

lisp - 关于 LISP 的问题

lisp - 在 Common Lisp 中转置列表

scheme - SICP/Scheme中的apply函数

functional-programming - Lisp - 在 n 个列表中拆分列表