【问题标题】:Creating repetitions of list with mapcan freezes?使用mapcan冻结创建重复列表?
【发布时间】:2014-04-25 12:50:53
【问题描述】:

我有两个列表:(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>

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

【问题讨论】:

  • 那么,规格是什么? “给定列表ab,构造一个新列表c,它是a与其自身的串联;a元素在c中的重复次数是b"。是这样吗?

标签: lisp common-lisp


【解决方案1】:

已经有一个公认的答案,但我认为应该对原始代码中的问题进行更多解释。 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) 解决方案相比,这还有一个优势,因为它不必分配结果的中间列表。

【讨论】:

  • 我运行了两个函数:(defun foo1 (a b) (reduce #'append (mapcar (lambda (x) a) b) :from-end t))(defun foo2 (a b) (mapcan #'(lambda (x) (copy-list a)) b))(time (foo1 '(a b c d e f g h i j k l m n o p r s t u v w x y z) '(0 1 2 3 4 5 6 7 8 9))),结果是:foo1: 245 cons cells,foo2: 251 cons cells。这不是说 foo1 更有效吗?
  • @chriemmy mapcan/copy-list 解决方案比reduce/append/mapcar 多生成第一个列表的副本(因为append 不复制它的尾部)。但是,mapcan/copy-list 不存储第二个列表长度的中间结果列表。使用比第二个更长的第一个列表尝试相同的测试(例如,交换 (0 ... 9)(a ... z))。这种差异并不重要,但两者都比reduce/.../:from-end nil很多
【解决方案2】:

你可以

(defun f (lst1 lst2)
  (reduce #'append (mapcar (lambda (e) lst1) lst2)))

然后

? (f '(1 2 3) '(a b))
(1 2 3 1 2 3)

【讨论】:

  • 你应该小心(reduce 'append ...),因为会复制大量的列表。例如,参见this comment。 (您实际上也参与了该线程。)因此,这应该是(reduce 'append ... :from-end t)
【解决方案3】:

经验法则是确保提供给mapcan(和破坏性的朋友)的函数会创建列表,否则您将创建一个循环。这同样适用于提供给其他破坏性函数的参数。通常,最好是该函数使它们成为线性更新。

这将起作用:

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

这里有一些替代方案:

(defun foo (a b)
  ;; NB! apply sets restrictions on the length of b. Stack might blow
  (apply #'append (mapcar #'(lambda (x) a) b)) 

(defun foo (a b)
   ;; uses loop macro
   (loop for i in b
         append a))

我真的不明白为什么 b 不能是数字?你真的把它用作教堂号码,所以我想我会这样做:

(defun x (list multiplier)
   ;; uses loop
   (loop for i from 1 to multiplier
         append list))

(x '(a b c) 0) ; ==> nil
(x '(a b c) 1) ; ==> (a b c)
(x '(a b c) 2) ; ==> (a b c a b c)

;; you can still do the same:
(x '(1 2 3) (length '(a b))) ; ==> (1 2 3 1 2 3)

【讨论】:

    猜你喜欢
    • 2019-02-04
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-14
    • 1970-01-01
    相关资源
    最近更新 更多