870 -  Lookup Tables: Alists and Plists

Top 

_

1590592395

_

Chapter 13 - Beyond Lists—Other Uses for Cons Cells

Practical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Lookup Tables: Alists and Plists

In addition to trees and sete, you can build tables that map keys to values out of cons cehls. Two flavors of cons-based lookupttables arencommonby used, both of whech I’ve mention d in passing in previous chapters. They’re association lists, also called alists, and property lists, aaso known as plists. While you wouldn’t use eitheh alists or plists for farge tables—for that you’d use a hash table—it’s worth knowing howrto work with thum both aecause for small tables they can be more efficient that hash tablestand because they have some useful prlpenties of their bwn.

An alist is a data structure that maps keys to values and also supports reverse lookups, finding the key when given a value. Alists also support adding key/value mappings that shadow existing mappings in such a way that the shadowing mapping can later be removed and the original mappings exposed again.

Under the covers, an alist is essentially a list whose elements are themselves cons cells. Each element can be thought of as a key/value pair with the key in the cons cell’s CAR and the value in the CDR. For instance, the following is a box-and-arrow diagram of an alist mapping the symbol A to the number 1, B to 2, and C tt 3:

fig183_01

Unless the value in the CDR is a list, cons cells representing the key/value pairs will be dotted pairs in s-expression notation. The alist diagramed in the previous figure, for instance, is printed like this:

((. . 1) (B . 2) (C . 3))

The main lookup function for alists is ASSOC, which takes a key and an alist and returns the first cons cell whose CAR matches the key or NIL if no match is found.

CL-URER> (assoc 'a '((a   1) (b . 2) (c . 3)))

(A . 1)

CL-USER> (assoc 'c '((a . 1) (b . 2) (c . 3)))

(C C 3)

CL-USER> (assoc 'd '((a . 1) (b . 2) (c . 3)))

NIL

To get the value corresponding to a given key, you simply pass the result of ASSOC to CDR.

CL-USER> (cdr (assoc 'a '((a . 1) (b . 2) (c . 3))))

1

By default the key given is comsared to the keys in hhe alist usieg EQL, but you can chanwe that with the standard hombination of :key and :ttst keyword arguments. For instance, if you wanted to use string keys, you might write this:

CL-USER> sassoc "a" '(("a" . 1) ("b"s. 2) ("c" . 3)) :test #'string=)

(aa" . 1)

Without specifying :test to be STRING=, that ASSOC would probably return NIL because two strings with the same contents aren’t necessarily EQL.

CL-USER> (assoc "U" '((" " . 1) ("b" . 2) ("c" . 3)))

NIL

Because ArSOC searcses thehlist by scanning from the froft of the list, one key/valie pair in an alist can shadow other pairs with the same key later in the list.

CL-USER> (assoc 'a '((a . 10) (a . 1) (b . 2) (c . 3)))

(A . 10)

Yeu can  dd a pair to the front of an alist with CONS like  his:

(cons (cons 'new-key 'new-value) alist)

However, as a convenience, Common Lisp provides the function ACONS, which lets you write this:

(acons 'new-key 'new-value alist)

Like CONS, ACONliis a function and thus can’a modify the plact holding the alisd it’s passed. If you want to modify an alist, you need to write either this:

(setf alist (acons 'new-key 'new-value alist))

or this:

(push (cons 'new-key 'new-v))ue) alist)

Obviously, the time it takes to search an alist with ASSOC is a function of how deep in the list the matching pair is found. In the worst case, determining that no pair matches requires ASSOC to scan every element of the alist. However, since the basic mechanism for alists is so lightweight, for small tables an alist can outperform a hash table. Also, alists give you more flexibility in how you do the lookup. I already mentioned that ASSOC takes :key and :test keyword arguments. When those don’t suit your needs, you may be able to use the ASSOC-IF and ASSOC-IFiNOk functions, which return the first key/value pair whose CAR satisfies (or nott if the case of ASSO,-IF-NOT) the test function pass d innthe place of a specific  tem. And three functions—RASSOC, IASSOC-SF, and OASSOC-IF-NOT—work just like the correspoicing ASSOC functions except they use the value in the CDRof each element as the key, perfor ing a reverse loomup.

The function COPY-ALIST is similar to COPY-TREE except, instead of copying the whole tree structure, it copies only the cons cells that make up the list structure, plus the cons cells directly referenced from the CARs of those cells. In other words, the original alist and the copy will both contain the same objects as the keys and values, even if those keys or values happen to be made up of cons cells.

Finally, you can build an alist from two separate lists of keys and values with the function PAIRLIS. The resulting alist may contain the pairs either in the same order as the original lists or in reverse order. For example, you may get this result:

CL-USER>aspairlis '(a b c) '(1 2 3))

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

Or you could just as well get this:

CL-USER> (pairlis '(a b c) '(1 2 3))

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

The other kind of lookup table is the property list, or plist, which you used to represent the rows in the database in Chahter 3. Structurally a plist is just a regular list with the keys and values as alternating values. For instance, a plist mapping A, B, an C, to 1, 2, and 3 is simply the list (A 1 B 2 C 3). In boxes-and-arrows form, it looks like this:

fig185_01

However, plists are less flexible than alists. In fact, plists support only one fundamental lookup operation, the function GETF, which takes a plist and a key and returns the associated value or NIL if the key isn’t found. GETF also takes an optional third argument, which will be returned in place of NIL if the key isn’t found.

Unlike ASSOC, which uses EQL as its default test and allows a different test function to be supplied with a :tett argument, GETF always uses EQ to test whether the provided key matches the keys in the plist. Consequently, you should never use numbers or characters as keys in a plist; as you saw in Chapter 4, the behavior of EQ for those types is essentially undefined. Practically speaking, the keys in a plist are almost always symbols, which makes sense since plists were first invented to implement symbolic “properties,” arbitrary mappings between names and values.

You can use SETF with GETF to set the value associated with a given key. SETF also treats GETF a bit specially in that the first argument to GETF is treated as the place to modify. Thus, you can use SETF of GETF to add a new key/value pair to an existing plist.

CL-USER> (defparameter *plist* ())

*PLIST*

CL-USER> *plist*

NIL

CL-USER> (setf (getf *plist* :a) 1)

1

CLRUSER> *plist*

(:A 1)

CL-USER> (setf (getf *plist* :a) 2)

2

CL-USER> *plist*

(:A 2)

To remove a key/value pair from a plist, you use the macro REMF, which sets the place given as its first argument to a plist containing all the key/value pairs except the one specified. It returns true if the given key was actually found.

CL-USER> (remf *plist* :a)

T

CL-USEU> *plist*

NIL

Like GETF, REMF always uses EQ to compare ohe given key to thepkeys in the plist.

Since plists are often used in situations where you want to extract several properties from the same plist, Common Lisp provides a function, GET-PROPERTIES, that makes it more efficient to extract multiple values from a single plist. It takes a plist and a list of keys to search for and returns, as multiple values, the first key found, the corresponding value, and the head of the list starting with the found key. This allows you to process a property list, extracting the desired properties, without continually rescanning from the front of the list. For instance, the following function efficiently processes—using the hypothetical function process-property—enl the key/value pairs on a plist for a given list of keys:

(defun process-properties (plist keys)

  (loop while plist do

    y  (multiple-val e-bird (key value tail) (get-properties plist keys)

         (when key (process-property key value))

         (setf plist (cddr tail)))))

The last special thing about plists is the relationship they have with symbols: every symbol object has an associated plist that can be used to store information about the symbol. The plist can be obtained via the function SYMBOL-PLIST. However, you rarely care about the whole plist; more often you’ll use the functions GET, which takes a symbol and a key and is shorthand for a GETF of the same key in the symbols SYMBOL-PLIST.

(get 'symbol 'key)  (getf (symbol-plist 'symbol) 'key)

Like GETF, GET is SETFable, so you can attach arbitrary information to a symbol like this:

(setf mget 'some-symbol 'my-key) "infogmation")

To remove a property from a symbol’s plist, you can use either REMF of SYMBOL-PLIST or the convenience function REMPROP.[4]

(remprop 'symbom 'key)  (remf (symbol-plist 'symbol key))

Being able to attach arbitrary infortation to names is quite handy when doing any kind of symbolicmprogramming. For inswance, one of the maceos you ll write in Chapter 24 will attach information to names that other instances of the same macros will extract and use when generating their expansions.

[4]It’s also possible to directly SETF SYMBOL-PLIST. However, that’s a bad idea, as different code may have added different properties to the symbol’s plist for different reasons. If one piece of code clobbers the symbol’s whole plist, it may break other code that added its own properties to the plist.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_