0

I have a list of lists: (setq xs (list (list 1 2 3) (list 4 5 6) (list 7 8 9))). I want to remove a first element from each list to get ((2 3) (5 6) (8 9)). It's easy to do it non-destructively: (mapcar 'cdr xs). But I want mutate the original list. I tried:

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

But it doesn't work. How to change each list of xs variable in-place, without creating any temporary lists, as efficiently as possible?

Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166

2 Answers2

6

Use 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))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
1

@Rainer Joswig's answer is correct, use map-into. The link gives example implementation using loop macro. If you want to implement map-into from scratch, or you use Emacs Lisp, you can also do it using dotimes. In Emacs Lisp dotimes is implemented in subr.el and doesn't require CL package. This is map-into with 1 sequence to map into the result sequence:

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

For version with variable amount of sequences we must sprinkle our code with apply and 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)))))

We see, however, that elt inside dotimes makes our algorithm work in O(n2). We can optimize it to work in O(n) by using mapl (thanks @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

The reason setf doesn't work inside mapcar is that setf is a complex macro that expands into expression that can manipulate the data it mutates. In a lambda scope inside mapcar it has access only to a variable, local to this lambda, not to the sequence passed to mapcar itself, so how should it know, where to put a modified value back? That's why mapcar code in the question returns modified list of lists but doesn't mutate it in-place. Just try (macroexpand '(setf (elt xs 0) (funcall 'cdr (elt xs 0)))) and see for yourself.

Community
  • 1
  • 1
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
  • 1
    Using `elt` to access the elements of the list like this is expensive. It makes the function in n2 time instead of n. It would be better to keep track of the tails of the list, so that you're always getting the first element. You could use [**mapl**](http://www.lispworks.com/documentation/HyperSpec/Body/f_mapc_.htm) to do this efficiently. E.g., to remove the first element from each of the lists, you could **(mapl (lambda (tail) (pop (first tail))) list)**. – Joshua Taylor Dec 27 '14 at 19:48
  • See edit. Do the new functions work in O(n)? Also, is it possible to simplify variable-argument map-into? – Mirzhan Irkegulov Dec 30 '14 at 17:07
  • The new functions are O(n), which is good. I think you can simplify the second implementation of **map-into**, though. E.g., see http://pastebin.com/W8tLkVBR. – Joshua Taylor Dec 31 '14 at 02:23