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