Processing items in a list

Lists provide a convenient way of representing collections of data, such as the notes in a tune, or the sequence of analogue values read from an input. This section introduces the map functions that are very useful for processing items in a list. It has been updated to include the new functions in Release 4.6.

The functions are summarised in the following table:

  Gives no output Gives a list of return values Concatenates return values 
Processes items mapc mapcar mapcan
Processes tails mapl maplist mapcon

The following sections explain when you might want to use each of the map functions. The first two, mapc and mapcar, are the most useful, but this section includes the other four for completeness:

Processing the items in a list - mapc

The function mapc performs a function on each item in one or more lists. For example:

> (mapc print '(0 1 2 3))

0 
1 
2 
3 
(0 1 2 3)

It returns the first list.

Here's a more interesting example. The following list tune represents the notes of a familiar tune:

(defvar tune '(0 0 7 7 9 9 7 nil 5 5 4 4 2 2 0))

We've used nil to insert a gap. The following function play plays a single note, or a gap if its argument is nil:

(defun play (x) (when x (note 3 x 4)) (delay 200) (note) (delay 200))

We can use mapc to apply the function to each of the items in the tune. The first argument is a function, and the second argument is the list we want to process:

(mapc play tune)

Connect a piezo speaker between pin 3 and GND on an Arduino Uno to hear the tune.

Alternatively, on an Arduino Mega 2560 connect the piezo speaker between pin 9 and GND and change the definition of play to:

(defun play (x) (when x (note 9 x 4)) (delay 200) (note) (delay 200)) 

Transforming a list - mapcar

The related function mapcar applies a specified function to each of the items in one or more lists, and returns the list of results. For example:

> (mapcar 1+ '(0 1 2 3 4))
(1 2 3 4 5)
We can create a function invert to invert a note x by changing it to (- 12 x):
(defun invert (x) (when x (- 12 x)))
Again, nil represents a gap which is left unchanged. We can use mapcar to invert the whole tune:
> (mapcar invert tune)
(12 12 5 5 3 3 5 nil 7 7 8 8 10 10 12)

or we can play the inverted tune with:

 (mapc play (mapcar invert tune))

Both mapc and mapcar can alternatively take a function of two arguments, and two lists, and apply the function to corresponding elements of both lists until the shortest list runs out. For example:

> (mapcar * '(1 2 3) '(4 5 6))
(4 10 18)

You can alternatively specify the function using a lambda expression. For example, to square each item in a list:

> (mapcar (lambda (x) (* x x)) '(1 2 3 4 5))
(1 4 9 16 25)

All the map functions can take multiple lists, and apply a function to corresponding elements of each list until the shortest list runs out. For example:

> (mapcar * '(1 2 3) '(4 5 6 7))
(4 10 18)

Concatenating results - mapcan

The function mapcan is like mapcar, except that the results should be lists, and they are destructively concatenated into a single list. It's useful when you want to return a list containing a subset of the items in the original list(s).

One use for mapcan is as a filter, to select elements from a list only if they meet a specified test. For example, to extract the numbers from a list:

> (mapcan (lambda (x) (if (numberp x) (list x))) '(1 a 2 b c 3 d 4))
(1 2 3 4)

This second example shows how mapcan can be used to interleave three lists:

> (mapcan list '(a b c d) '(1 2 3 4) '(w x y z))
(a 1 w b 2 x c 3 y d 4 z)

Applying a function to successive cdrs - mapl

The function mapl is like mapc, except that the function is applied to the lists and then successive cdrs of those lists, rather then successive elements of the lists. It's useful when you need to look ahead into the list to know what to do.

One application of mapl is when you want to process the last item of a list differently; for example:

> (mapl 
   (lambda (x) (if (cdr x) (format t "~a, " (car x)) (format t "and ~a." (car x))))
   '(1 2 3 4))
1, 2, 3, and 4.
(1 2 3 4)

Another application is when you want to modify an existing list using setf, incf, or decf. For example, to increment each element of a list mylist:

> (let ((mylist '(1 2 3 4)))
    (mapl 
     (lambda (x) (incf (car x)))
     mylist))
(2 3 4 5)

Transforming successive cdrs - maplist

The function maplist is like mapl, except that it returns a list of the results. Like mapl, it's useful when you need to look ahead into the list to know what to do.

The following example sums the numbers in a list, followed by all but the first, and so on down to the last:

> (maplist (lambda (x) (apply + x)) '(1 2 3 4 5))
(15 14 12 9 5)

Concatenating results from successive cdrs - mapcon

The function mapcon is like maplist, except that the results should be lists, and they are destructively concatenated into a single list. Like mapcan, it's useful when you want to return a list containing a subset of the items in the original list(s).

One application is to extract duplicate values from a sorted list:

> (mapcon 
   (lambda (x) (when (eq (first x) (second x)) (list (car x)))) 
   '(1 2 3 3 5 7 7 8 9 11 11))
(3 7 11)

By changing the when to unless you could instead return a list with the duplicate values removed.

Here's a second nice example that uses mapcon in conjunction with mapcar to create a list of all pairs of a list of elements:

> (mapcon
   (lambda (x)
     (mapcar 
      (lambda (y) (list (car x) y))
      (cdr x)))
   '(a b c d e))
((a b) (a c) (a d) (a e) (b c) (b d) (b e) (c d) (c e) (d e))

Previous: Manipulating lists

Next: Recursion