You can think of a mapping function as a kind of special purpose iterator. Every mapping function expects you to supply a function. A typical mapping function applies your function to every element of the supplied list(s). One variation on this theme applies your function to successive sublists.
A sequence is a generalization of the list data type. Vectors (one-dimensional arrays) and lists are specializations of the sequence data type. Some mapping functions work only with lists as inputs, while others accept sequences.
The first group of mapping functions processes successive elements of lists. The mapping functions in this group differ in how they construct a return value.
MAPCAR
processes successive elements of one or more supplied
lists. You must supply a function that accepts as many arguments as the
number of lists you supply to MAPCAR
, which applies your function to successive elements
and combines the function's results into a freshly constructed list. The
mapping stops upon reaching the end of the shortest list; MAPCAR
's
result has as many elements as the shortest input list.
MAPC
does not combine the results of applying your
function to successive elements of the input list(s). Instead, it
processes the inputs just for effect, and returns the first input list as the
result of MAPC
.
MAPCAN
combines results using the destructive function NCONC
.
Since NCONC
-- like its nondestructive counterpart APPEND
-- expects its arguments to be lists, the function you supply to MAPCAN
must always return a list.
? (mapcar #'atom (list 1 '(2) "foo" nil))
(T NIL T T)
? (mapcar #'+ (list 1 2 3) (list 4 5 6))
(5 7 9)
? (mapc #'(lambda (x y) (print (* x y))) (list 1 0 2) (list 3 4 5))
3
0
10
(1 0 2)
? (mapcan #'list (list 1 2 3) (list 4 5 6))
(1 4 2 5 3 6)
? (mapcan #'(lambda (a b) (list (cons a b))) (list 1 2 3) (list 4 5 6))
((1 . 4) (2 . 5) (3 . 6))
MAPLIST
processes successive sublists of one or more
supplied lists. You must supply a function that accepts as many arguments
as the number of lists you supply to MAPLIST
, which
applies your function to successive sublists and combines the function's
results into a freshly constructed list. The mapping stops upon reaching
the end of the shortest list; MAPLIST
's result has as many elements as the shortest input
list.
MAPL
does not combine the results of applying your
function to successive sublists of the input list(s). Instead, it
processes the inputs just for effect, and returns the first input list as the
result of MAPL
.
MAPCON
combines results using the destructive function NCONC
.
Since NCONC
-- like its nondestructive counterpart APPEND
-- expects its arguments to be lists, the function you supply to MAPCON
must always return a list.
? (maplist #'list (list 1 2 3) (list 4 5 6))
(((1 2 3) (4 5 6)) ((2 3) (5 6)) ((3) (6)))
? (mapl #'(lambda (x y) (print (append x y))) (list 1 0 2) (list 3 4 5))
(1 0 2 3 4 5)
(0 2 4 5)
(2 5)
(1 0 2)
? (mapcon #'list (list 1 2 3) (list 4 5 6))
((1 2 3) (4 5 6) (2 3) (5 6) (3) (6))
A sequence is either
a list or a vector (a one-dimensional array). The previous group of
mapping functions (MAPCAR
et al) processes successive CARs or CDRs
of their input lists. MAP
processes successive elements of its input
sequences.
MAP
requires that you specify the type of its result
using one of the following designators:
Designator Result
---------- ------
NIL NIL
'LIST a list
'VECTOR a vector
Note that you can
also specify subtypes of LIST
or VECTOR
-- your Lisp implementation may be able to optimize
the storage of the result based on the type you specify.
? (map nil #'+ (list 1 2 3) (list 4 5 6))
NIL
? (map 'list #'+ (list 1 2 3) (list 4 5 6))
(5 7 9)
? (map 'vector #'+ (list 1 2 3) (list 4 5 6))
#(5 7 9)
? (map '(vector number 3) #'+ (list 1 2 3) (list 4 5 6))
#(5 7 9)
A filter passes some
of its inputs through to its output, and drops others. We can use mapping functions
to implement filters by taking note of the behavior of APPEND
:
? (append '(1) nil '(3) '(4))
(1 3 4)
The NIL
argument (which is equivalent to the empty list) simply "disappears"
from the result. This is the key observation that we need to construct a
filter. We'll use MAPCAN
to map over our input list(s) and supply a mapping
function that:
· makes a list of each result we wish to retain in the output, or
·
returns NIL
in place of each input we wish to exclude from the output.
? (defun filter-even-numbers (numbers)
(mapcan #'(lambda (n) (when (evenp n) (list n))) numbers))
FILTER-EVEN-NUMBERS
? (filter-even-numbers (list 1 2 3 4 5 6 7 8))
(2 4 6 8)
WHEN
returnsNIL
if the condition isNIL
. We could have written(if (evenp n) (list n) nil)
instead.
Here's a slightly more complex filter that returns a list of evenly divisible pairs of numerators and denominators:
? (defun filter-evenly-divisible (numerators denominators)
(mapcan #'(lambda (n d)
(if (zerop (mod n d))
(list (list n d))
nil))
numerators denominators))
? (filter-evenly-divisible (list 7 8 9 10 11 12)
(list 1 4 5 5 2 3))
((7 1) (8 4) (10 5) (12 3))
Most Lisp systems
will generate more efficient code to call a function that is known during
compilation than a function that can change at run time. Mapping
functions accept a functional argument, and most compilers will generate code
that supports run time function binding -- even if you specify a
"constant" function, such as #'+
. Also, the run
time call may incur extra overhead to generate a list of arguments for the
function's application.
Therefore, if you are concerned about efficiency you should write map-like functions using iteration instead of mapping functions. But do this only when you are sure that efficiency is an issue for the portion of the program you intend to rewrite.
Sometimes you may need to apply a test to some input sequences and return a truth value based upon what the test returned for all of the inputs. For example, you might want to know whether any number in a sequence is outside of a specified range, or whether every word is at least five letters long. You could construct these tests from the mapping functions described above, but that would be more verbose (and less efficient) than using the predicate mapping functions provided by Lisp.