2014-12-27 42 views
0

我有一個列表清單:(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 in-place:破壞性修改列表

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

但它不起作用。如何儘可能高效地更改每個xs變量列表,而不創建任何臨時列表?

回答

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

@Rainer Joswig的答案是正確的,使用map-into。該鏈接給出了使用loop宏的示例實現。如果你想從頭開始實現map-into,或者你使用Emacs Lisp,你也可以使用dotimes來完成。 Emacs Lisp dotimessubr.el中實現,不需要CL package。這是map-into 1個序列映射到結果序列:

​​

對於帶有序列的變化量,我們必須灑我們的代碼與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 )中工作。我們可以通過使用mapl(感謝@Joshua Taylor)來優化它在O(n)中的工作。

(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 

原因setf不內部mapcar工作是setfcomplex macro一個擴展到表達,可以操縱它變異的數據。在mapcar之內的lambda範圍內,它只能訪問變量,局部於此lambda,而不訪問傳遞給mapcar本身的序列,所以它應該如何知道將修改後的值放回到哪裏?這就是爲什麼mapcar問題中的代碼返回列表的修改列表,但不會在原地進行變異。試試(macroexpand '(setf (elt xs 0) (funcall 'cdr (elt xs 0))))並親自體驗。

+1

使用'elt'來訪問列表中的元素是非常昂貴的。它使得函數的時間代替了n。跟蹤列表的尾巴會更好,這樣你總能得到第一個元素。您可以使用[** mapl **](http://www.lispworks.com/documentation/HyperSpec/Body/f_mapc_.htm)高效地完成此操作。例如,要從每個列表中刪除第一個元素,你可以**(mapl(lambda(tail)(pop(first tail)))list)**。 –

+0

請參閱編輯。新功能在O(n)中工作嗎?另外,是否有可能將變量參數映射簡化爲? –

+0

新功能是O(n),這很好。不過,我認爲你可以簡化** map-into的第二個實現。例如,請參閱http://pastebin.com/W8tLkVBR。 –