As you’ve seen so far, a lot can be accomplished in Lisp by using cons cells, symbols, strings, and numeric datatypes. As a very mature language, Common Lisp contains many more datatypes that move well beyond these basics. In this chapter, we will discuss the most useful of these advanced datatypes, including arrays, hash tables, and structures.
The Common Lisp array is very similar to a list. The main advantage of using arrays is that they require only a constant amount of time to access a value at any specific location. We’ll be discussing what this means shortly.
To create a new array, use the make-array
command, specifying the array’s size:
> (make-array 3)
#(NIL NIL NIL)
This creates an array of length 3. In order to indicate that the value created is not just a list, Common Lisp prepends a hash mark (#) in front of the array.
To get and set items in an array, use the aref
function. For example, here’s how we get the item at index 1:
>(defparameter x (make-array 3))
#(NIL NIL NIL) >(aref x 1)
NIL
Of course, our array is just filled with nil
s right now, so there’s not much worth getting. To set items in the array to more interesting values, use aref
in conjunction with the setf
command:
>(defparameter x (make-array 3))
#(NIL NIL NIL) >(setf (aref x 1) 'foo)
FOO >x
#(NIL FOO NIL) >(aref x 1)
FOO
Although aref
is usually a command used to get a value out of an array, when used in this special way indicated in the example, it lets us set a value in an array, instead. This ability to use the setf
and aref
commands together shows off a feature in Common Lisp: its support for generic programming. Let’s take a closer look at the setf
command to learn more about how this feature works.
The Common Lisp language is said to support generic setters. This means that in most cases, the code for pulling a value out of a data structure (whether an array, list, string, or something else) is identical to the code for putting data into that same data structure. The setf
command can be used in conjunction with functions that perform getting operations and can use the same functions to perform setting operations, instead.
We’ve already seen that aref
can be used to get values out of an array, and when used with setf
, it can be used for setting values in the same array. The setf
command can perform this trick in a general way across most of the commands in Common Lisp that get items from a data structure. Take, for instance, the following example involving a list:
>(setf foo '(a b c))
(A B C) >(second foo)
B >(setf (second foo) 'z)
Z > foo (A Z C)
As you would expect, the expression (second foo)
returns B
. But, when we pass (second foo)
to the setf
command , it somehow knows where the B
came from, and it is able to treat the expression (second foo)
as if it were a regular variable. Basically, the setf
command asks itself the question, “Where did the item in my first argument originally come from?” In this case, the value came from the second item in the list named foo
. Therefore, if we try to setf
this location, the source variable, foo
, is modified in response.
In fact, the first argument in setf
is a special sublanguage of Common Lisp, called a generalized reference. Not every Lisp command is allowed in a generalized reference, but you can still put in some pretty complicated stuff:
>(setf foo (make-array 4))
#(NIL NIL NIL NIL) >(setf (aref foo 2) '(x y z))
(X Y Z) >foo
#(NIL NIL (X Y Z) NIL) >(setf (car (aref foo 2)) (make-hash-table))
#S(HASH-TABLE) >(setf (gethash 'zoink (car (aref foo 2))) 5)
5 >foo
#(NIL NIL (#S(HASH-TABLE (ZOINK . 5)) Y Z) NIL)
This example demonstrates the true power of setf
in Common Lisp. In the first use, we put the list (x y z)
into an array as the third item . If we now print foo
, we can see that it worked . In the second use, we replace the first item in this list inside the foo
array with a hash table . Hash tables are another advanced data type we’ll be learning about shortly, in Hash Tables. It is surprisingly easy to do this with setf
, because the generalized reference in the first argument to setf
can be arbitrarily complicated.
Finally, we go all out and insert the value 5
into this hash table with the key of zoink
. The gethash
function lets you get a value out of a hash table, as we’ll see shortly. Here, with the help of setf
, we are putting the number 5 into the hash table instead.
I hope you can appreciate from this example how useful setf
can be when modifying complicated data structures in a program.
Another cool feature of setf
is that you can expand the generalized reference syntax to support new ways of accessing values. setf
is a truly generic way of modifying values, regardless of the level of nesting or the datatypes being used.
You’ve now seen some basic examples of working with arrays in Lisp. However, to fully understand the benefits of arrays, we need to compare them with lists.
Almost anything that can be done with a list can also be done with an array. However, arrays are usually much faster than lists when accessing specific elements, so the difference is in performance.
For example, the array-handling function aref
is very similar to a list-handling function called nth
, which allows you access to an item at a specific location in a regular list without using an array. Here is an example using nth
on a list:
> (nth 1 '(foo bar baz))
BAR
However, it makes sense to use the nth
function only with very small lists. If, for example, list X had thousands of items in it, running the command (nth 1000 x)
would be excruciatingly slow, because Lisp lists are made out of chains of cons cells. Hence, the only way Lisp can find the thousandth item in a list is to churn through the 999 preceding objects first.
In contrast, running the command (aref x 1000)
on a large array accesses the thousandth item directly, without counting through the previous 999 items. This means aref
will execute much more quickly on a large array than the nth
command would on a large list. In fact, an aref
call will happen very quickly no matter how large the array. Even if you had an array with a billion items, retrieving the last item would still happen very quickly. The only real limiting factor is your system: the amount of RAM your computer has and how capable your Lisp environment is of taking advantage of it.
Not only can we quickly access array values, but we can also change values at any specific location, usually faster than we can by performing the same operations on a list.
Because setting and getting specific values in a large data structure is so important, keep arrays in mind as a tool to help you get the best performance possible with your code.
18.118.128.105