the CDR
references until reaching a NIL
. The elements of the list are the objects referenced by the CAR
s of the cons cells in the list structure. If a cons cell in the list structure has a CAR
that references another cons cell, the referenced cons cell is considered to be the head of a list that's an element of the outer list.[145] CAR
and CDR
references for as long as they point to other cons cells. The values in a tree are thus the atomic—non-cons-cell- values referenced by either the CAR
s or the CDR
s of the cons cells in the tree structure.
For instance, the following box-and-arrow diagram shows the cons cells that make up the list of lists: ((1 2) (3 4) (5 6))
. The list structure includes only the three cons cells inside the dashed box while the tree structure includes all the cons cells.
To see the difference between a list function and a tree function, you can consider how the functions COPY-LIST
and COPY-TREE
will copy this bunch of cons cells. COPY-LIST
, as a list function, copies the cons cells that make up the list structure. That is, it makes a new cons cell corresponding to each of the cons cells inside the dashed box. The CAR
s of each of these new cons cells reference the same object as the CAR
s of the original cons cells in the list structure. Thus, COPY-LIST
doesn't copy the sublists (1 2)
, (3 4)
, or (5 6)
, as shown in this diagram:
COPY-TREE
, on the other hand, makes a new cons cell for each of the cons cells in the diagram and links them together in the same structure, as shown in this diagram:
Where a cons cell in the original referenced an atomic value, the corresponding cons cell in the copy will reference the same value. Thus, the only objects referenced in common by the original tree and the copy produced by COPY-TREE
are the numbers 5, 6, and the symbol NIL
.
Another function that walks both the CAR
s and the CDR
s of a tree of cons cells is TREE- EQUAL
, which compares two trees, considering them equal if the tree structure is the same shape and if the leaves are EQL
(or if they satisfy the test supplied with the :test
keyword argument).
Some other tree-centric functions are the tree analogs to the SUBSTITUTE
and NSUBSTITUTE
sequence functions and their -IF
and -IF-NOT
variants. The function SUBST
, like SUBSTITUTE
, takes a new item, an old item, and a tree (as opposed to a sequence), along with :key
and :test
keyword arguments, and it returns a new tree with the same shape as the original tree but with all instances of the old item replaced with the new item. For example:
CL-USER> (subst 10 1 '(1 2 (3 2 1) ((1 1) (2 2))))
(10 2 (3 2 10) ((10 10) (2 2)))
SUBST-IF
is analogous to SUBSTITUTE- IF
. Instead of an old item, it takes a one-argument function—the function is called with each atomic value in the tree, and whenever it returns true, the position in the new tree is filled with the new value. SUBST-IF-NOT
is the same except the values where the test returns NIL
are replaced. NSUBST
, NSUBST-IF
, and NSUBST-IF-NOT
are the recycling versions of the SUBST
functions. As with most other recycling functions, you should use these functions only as drop-in replacements for their nondestructive counterparts in situations where you know there's no danger of modifying a shared structure. In particular, you must continue to save the return value of these functions since you have no guarantee that the result will be EQ
to the original tree.[146]
Sets can also be implemented in terms of cons cells. In fact, you can treat any list as a set—Common Lisp provides several functions for performing set-theoretic operations on lists. However, you should bear in mind that because of the way lists are structured, these operations get less and less efficient the bigger the sets get.
That said, using the built-in set functions makes it easy to write set-manipulation code. And for small sets they may well be more efficient than the alternatives. If profiling shows you that these functions are a performance bottleneck in your code, you can always replace the lists with sets built on top of hash tables or bit vectors.
To build up a set, you can use the function ADJOIN
. ADJOIN
takes an item and a list representing a set and returns a list representing the set containing the item and all the items in the original set. To determine whether the item is present, it must scan the list; if the item isn't found, ADJOIN
creates a new cons cell holding the item and pointing to the original list and returns it. Otherwise, it returns the original list.
ADJOIN
also takes :key
and :test
keyword arguments, which are used when determining whether the item is present in the original list. Like CONS
, ADJOIN
has no effect on the original list—if you want to modify a particular list, you need to assign the value returned by ADJOIN
to the place where the list came from. The modify macro PUSHNEW
does this for you automatically.
CL-USER> (defparameter *set* ())
*SET*
CL-USER> (adjoin 1 *set*)
(1)
CL-USER> *set*
NIL
CL-USER> (setf *set* (adjoin 1 *set*))
(1)
CL-USER> (pushnew 2 *set*)
(2 1)
CL-USER> *set*
(2 1)
CL-USER> (pushnew 2 *set*)
(2 1)
You can test whether a given item is in a set with MEMBER
and the related functions MEMBER-IF
and MEMBER-IF- NOT
. These functions are similar to the sequence functions FIND
, FIND-IF
, and FIND-IF-NOT
except they can be used only with lists. And instead of returning the item when it's present, they return the cons cell containing the item—in other words, the sublist starting with the desired item. When the desired item isn't present in the list, all three functions return NIL
.