我有一份清单清单:(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
变量列表,而不创建任何临时列表?
答案 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 dotimes
在subr.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)))))
对于序列数量可变的版本,我们必须使用apply
和mapcar
来填充我们的代码:
(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
mapcar
在setf
内无效的原因是mapcar
是setf
,它扩展为可以操纵其变异的数据的表达式。在mapcar
内的lambda范围内,它只能访问本lambda的一个变量,而不能访问传递给mapcar
本身的序列,那么它应该如何知道,将修改后的值放回去的位置呢?这就是为什么问题中的(macroexpand '(setf (elt xs 0) (funcall 'cdr (elt xs 0))))
代码返回修改后的列表列表但不会就地改变它的原因。只需尝试{{1}}并亲眼看看。