Here's a simple example of using mapalist to map over an alist
much like mapcar maps over lists.
mapl/maplist/mapcon,mapal/mapalist/mapacon,mapal*/mapalist*/mapacon*,mappl/mapplist/mappcon,mapv/mapvector/mapvcon,mapt/maptimes/maptcon andmapt*/maptimes*/maptcon* is trivial.
A summary table of these mapcar-like functions is available.
See MAPCAR-like Overview.
(mapalist (lambda (key value)
(list key (- value)))
'((a . 1) (b . 2) (c . 3)))
==
(loop for (key . value) in '((a . 1) (b . 2) (c . 3))
collect (list key (- value)))
⇒ ((A -1) (B -2) (C -3))
Convert a "cdr-valued" alist to a "cadr-valued" alist.
(mapalist #'list '((a . 1) (b . 2) (c . 3)))
==
(loop for (key . value) in '((a . 1) (b . 2) (c . 3))
collect (list key value))
⇒ ((A 1) (B 2) (C 3))
Convert a "cadr-valued" alist to a plist.
(mapacon* #'list '((a 1) (b 2) (c 3)))
==
(loop for (key value) in '((a 1) (b 2) (c 3))
nconc (list key value))
⇒ (A 1 B 2 C 3)
Convert a plist to a "cdr-valued" alist.
(mapplist #'cons '(a 1 b 2 c 3))
==
(loop for (key value) on '(a 1 b 2 c 3) by #'cddr
collect (cons key value))
⇒ ((A . 1) (B . 2) (C . 3))
Merge two "cdr-valued" alists into a new one by a process of alternation.
(mapacon (lambda (key1 value1 key2 value2)
(list (cons key1 value1) (cons key2 value2)))
'((a . 1) (b . 2) (c . 3))
'((x . -1) (y . -2) (z . -3)))
==
(loop for (key1 . value1) in '((a . 1) (b . 2) (c . 3))
for (key2 . value2) in '((x . -1) (y . -2) (z . -3))
collect (list (cons key1 value1) (cons key2 value2)))
⇒ ((A . 1) (X . -1) (B . 2) (Y . -2) (C . 3) (Z . -3))
This function, used in a few examples below, will handle any
number of alists or plists automatically. The lambda form above
could simply be replaced by (grouper-by-two #'cons).
(defun grouper-by-two (group-function)
(lambda (&rest args)
(mappcon (lambda (key value)
(list (funcall group-function key value)))
args)))
Merge any number of alists (“cdr-valued” or “cadr-valued”) and plists into an alist (“cdr-valued” or “cadr-valued”) or plist by alternation.
(mapacon (grouper-by-two #'cons)
'((a . 1) (b . 2))
'((x . -1) (y . -2))
'((foo . 8) (bar . 16)))
==
;; As far as I know, this is the best loop can do here.
;; But the real point is how Loopless simplifies and uniformizes
;; processing of alists (both types) and plists.
(loop with group = (grouper-by-two #'cons)
for (key1 . value1) in '((a . 1) (b . 2))
for (key2 . value2) in '((x . -1) (y . -2))
for (key3 . value3) in '((foo . 8) (bar . 16))
nconc (apply group (list key1 value1 key2 value2 key3 value3)))
⇒ ((A . 1) (X . -1) (FOO . 8) (B . 2) (Y . -2) (BAR . 16))
But what if the data is in 3 different formats instead of all being in the same one as above? No problem, just do some preconversion.
You might balk at the “inefficiency” of this solution, but keep in mind that the chances of you having to deal with such a scenario in a performance-critical situation where the slowdown introduced by the copying would matter are very slim. If that happens, time for some custom programming!
(mapacon (grouper-by-two #'cons)
'((a . 1) (b . 2))
(mapalist* #'cons '((x -1) (y -2)))
(mapplist #'cons '(foo 8 bar 16)))
==
(loop with group = (grouper-by-two #'cons)
for (key1 . value1) in '((a . 1) (b . 2))
for (key2 value2) in '((x -1) (y -2))
for (key3 value3) on '(foo 8 bar 16) by #'cddr
nconc (apply group (list key1 value1 key2 value2 key3 value3)))
⇒ ((A . 1) (X . -1) (FOO . 8) (B . 2) (Y . -2) (BAR . 16))
There's an example involving mapping over alists and accumulation of
results into multiple lists with with-collectors in the
Advanced Examples section.