Mapcar就地:破坏性地修改列表列表

时间:2014-12-27 10:41:33

标签: lisp common-lisp in-place

我有一份清单清单:(setq xs (list (list 1 2 3) (list 4 5 6) (list 7 8 9)))。我想从每个列表中删除第一个元素以获取((2 3) (5 6) (8 9))。非破坏性的做法很容易:(mapcar 'cdr xs)。但我想改变原始列表。我试过了:

(mapcar (lambda (x) (setf x (cdr x))) xs)
(mapcar (lambda (x) (pop x)) xs)

但它不起作用。如何尽可能有效地更改每个xs变量列表,而不创建任何临时列表?

2 个答案:

答案 0 :(得分:6)

使用MAP-INTO

CL-USER 16 > (let ((s (list (list 1 2 3)
                            (list 4 5 6)
                            (list 7 8 9))))
               (map-into s #'rest s))
((2 3) (5 6) (8 9))

答案 1 :(得分:0)

@Rainer Joswig的回答是正确的,请使用map-into。该链接使用loop宏提供示例实现。如果您想从头开始实现map-into,或者您使用Emacs Lisp,您也可以使用dotimes来实现。在Emacs中,Lisp dotimessubr.el中实现,不需要CL package。这是map-into,其中1个序列映射到结果序列中:

(defun map-into (r f xs)
  (dotimes (i (min (length r) (length xs)) r)
    (setf (elt r i)
          (funcall f (elt xs i)))))

对于序列数量可变的版本,我们必须使用applymapcar来填充我们的代码:

(defun map-into (r f &rest xss)
  (dotimes (i (apply 'min (length r) (mapcar 'length xss)) r)
    (setf (elt r i)
          (apply f (mapcar (lambda (s) (elt s i))
                           xss)))))

然而,我们看到elt内的dotimes使我们的算法在O(n 2 )中工作。我们可以使用mapl优化它以在O(n)中工作(感谢@Joshua Taylor)。

(defun map-into (rs f xs)
  (mapl (lambda (r x) (setf (car r) (funcall f (car x)))) rs xs))

(defun map-into (rs f &rest xss)
  (mapl (lambda (r xs)
          (setf (car r)
                (apply f (car xs))))
        rs
        (apply 'mapcar 'list xss))) ;; transpose a list of lists

mapcarsetf内无效的原因是mapcarsetf,它扩展为可以操纵其变异的数据的表达式。在mapcar内的lambda范围内,它只能访问本lambda的一个变量,而不能访问传递给mapcar本身的序列,那么它应该如何知道,将修改后的值放回去的位置呢?这就是为什么问题中的(macroexpand '(setf (elt xs 0) (funcall 'cdr (elt xs 0))))代码返回修改后的列表列表但不会就地改变它的原因。只需尝试{{1}}并亲眼看看。