Mapping a function over two lists in elisp
Asked Answered
A

3

19

In common lisp I can do this:

(mapcar #'cons '(1 2 3) '(a b c))

=> ((1 . A) (2 . B) (3 . C))

How do I do the same thing in elisp? When I try, I get an error:

(wrong-number-of-arguments mapcar 3)

If elisp's mapcar can only work on one list at a time, what is the idomatic way to combine two lists into an alist?

Aerator answered 27/1, 2012 at 3:42 Comment(0)
M
22

You want mapcar*, which accepts one or more sequences (not just lists as in Common Lisp), and for one sequence argument works just like the regular mapcar.

(mapcar* #'cons '(1 2 3) '(a b c))
((1 . A) (2 . B) (3 . C))

And even if it weren’t defined, you could easily roll your own:

(defun mapcar* (f &rest xs)
  "MAPCAR for multiple sequences"
  (if (not (memq nil xs))
    (cons (apply f (mapcar 'car xs))
      (apply 'mapcar* f (mapcar 'cdr xs)))))
Motet answered 27/1, 2012 at 4:46 Comment(1)
Since it's part of cl-macs, you should call it as cl-mapcar and make sure to (require 'cl-lib).Grus
E
18

Emacs has built-in Common Lisp library, which introduces plenty of Common Lisp functions and macros, but with the cl- prefix. There is no reason to avoid this library. cl-mapcar is what you want:

(cl-mapcar '+ '(1 2 3) '(10 20 30)) ; (11 22 33)

With dash list manipulation library (see the installation instructions), you can use -zip-with (remember: -zip-with is the same as cl-mapcar applied to 2 lists):

(-zip-with '+ '(1 2 3) '(10 20 30)) ; (11 22 33)

I don't know an elegant way to implement a -zip-with equivalent for 3 arguments. But you may use -partial from dash-functional package, which comes with dash (functions from dash-functional require Emacs 24). -partial partially applies the function, so these 2 function invocations below are equivalent:

(-zip-with '+ '(1 2) '(10 20)) ; (11 22)
(funcall (-partial '-zip-with '+) '(1 2) '(10 20)) ; (11 22)

Then, you can use it with a -reduce function:

(-reduce (-partial '-zip-with '+) '((1 2 3) (10 20 30) (100 200 300))) 
; (111 222 333)

You can wrap it into a function with &rest keyword, so this function would accept varying amount of arguments instead of a list:

(defun -map* (&rest lists)
  (-reduce (-partial 'zip-with '+) lists))
Enterectomy answered 3/8, 2014 at 22:55 Comment(0)
Z
1

These days, you can use seq-mapn

(seq-mapn #'cons '(1 2 3) '(a b c))
((1 . a) (2 . b) (3 . c))
Zygophyte answered 4/7 at 4:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.