【问题标题】:Mapcar in-place: destructively modify a list of listsMapcar in-place:破坏性地修改列表列表
【发布时间】:2014-12-27 10:41:32
【问题描述】:

我有一个列表列表:(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 变量列表?

【问题讨论】:

    标签: lisp common-lisp in-place


    【解决方案1】:

    使用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))
    

    【讨论】:

      【解决方案2】:

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

      但是,我们看到 eltdotimes 使我们的算法在 O(n2) 中运行。我们可以使用 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
      

      setfmapcar 中不起作用的原因是setf 是一个complex macro,它扩展为可以操纵它变异的数据的表达式。在 mapcar 内部的 lambda 范围内,它只能访问此 lambda 本地的变量,而不是传递给 mapcar 本身的序列,那么它应该如何知道将修改后的值放回哪里?这就是为什么问题中的mapcar 代码会返回修改后的列表列表,但不会就地改变它。试试(macroexpand '(setf (elt xs 0) (funcall 'cdr (elt xs 0)))) 自己看看吧。

      【讨论】:

      • 像这样使用elt 访问列表的元素是昂贵的。它使函数在 n2 时间内完成,而不是 n。最好跟踪列表的尾部,以便您始终获得第一个元素。您可以使用mapl 有效地执行此操作。例如,要从每个列表中删除第一个元素,您可以 (mapl (lambda (tail) (pop (first tail))) list).
      • 查看编辑。新函数在 O(n) 中有效吗?另外,是否可以简化变量参数映射到?
      • 新函数是O(n),很好。不过,我认为您可以简化 map-into 的第二个实现。例如,见pastebin.com/W8tLkVBR
      猜你喜欢
      • 2021-01-31
      • 1970-01-01
      • 2013-07-27
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多