Introduction to newLISP/Lists

= Lists =

Lists are used everywhere in newLISP - LISP stands for list processing - so it's not surprising that there are many useful functions for working with lists. It's quite hard to organize them all into one logical descriptive narrative, but here's an introduction to most of them.

A good thing about newLISP is that many of the functions that work on lists also work on strings, so you'll meet many of these again in the next chapter, where they'll be applied to strings.

Building lists
You can build a list and assign it to a symbol directly. Quote the list to stop it being evaluated immediately:

Lists are often created for you by other functions, but you can build your own too, using one of these functions:


 * list makes a new list from expressions


 * append glues lists together to form a new list


 * cons prepends an element to a list or makes a list


 * push inserts a new member in a list


 * dup duplicates an element

list, append, and cons
Use list to build a list from a sequence of expressions:

Notice that the (3 4) element is itself a list, which is nested inside the main list.

cons accepts exactly two expressions, and can do two jobs: insert the first element at the start of an existing list, or build a new two element list. In both cases it returns a new list. newLISP automatically chooses which job to do depending on whether the second element is a list or not.

To glue two or more lists together, use append:

Notice the difference between list and append when you join two lists:

list preserves the source lists when making the new list, whereas append uses the elements of each source list to make a new list.

To remember this: List keeps the List-ness of the source lists, but aPPend Picks the elements out and Packs them all together again.

append can also assemble a bunch of strings into a new string.

push: pushing items onto lists
push is a powerful command that you can use to make a new list or insert an element at any location in an existing list. Pushing an element at the front of the list moves everything one place to the right, whereas pushing an element at the end of the list just attaches it and makes a new last element. You can also insert an element anywhere in the middle of a list.

Despite its constructive nature, it's technically a destructive function, because it changes the target list permanently, so use it carefully. It returns the value of the element that was inserted, rather than the whole list.

When you refer to the location of an element in a list, you use zero-based numbering, which you would expect if you're an experienced programmer:



If you don't specify a location or index, push pushes the new element at the front. Use a third expression to specify the location or index for the new element. -1 means the last element of the list, 1 means the second element of the list counting from the front from 0, and so on:

If the symbol you supply as a list doesn't exist, push usefully creates it for you, so you don't have to declare it first.

(1) (1 2) (1 2 3) (1 2 3 4) (1 2 3 4 5) (1 2 3 4 5 6) (1 2 3 4 5 6 7) (1 2 3 4 5 6 7 8) (1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9 10)

By the way, there are plenty of other ways of generating a list of unsorted numbers. You could also do a number of random swaps like this:

although it would be even easier to use randomize:

(That's one of the nice things about newLISP - a more elegant solution is just a re-write away!)

push has an opposite, pop, which destructively removes an element from a list, returning the removed element. We'll meet pop and other list surgery functions later. See List surgery.

These two functions, like many other newLISP functions, work on strings as well as lists. See push and pop work on strings too.

dup: building lists of duplicate elements
A useful function called dup lets you construct lists quickly by repeating elements a given number of times:

There's a trick to get dup to return a list of strings. Because dup can also be used to duplicate strings into a single longer string, you supply an extra true value at the end of the list, and newLISP creates a list of strings rather than a string of strings.

Working with whole lists
Once you've got a list, you can start to work on it. First, let's look at the functions that operate on the list as a unit. After that, I'll look at the functions that let you carry out list surgery - operations on individual list elements.

Using and processing lists
dolist works through every item of a list:

A E I O U

In this example, apply expects a function and a list, and uses the elements of that list as arguments to the function. So it repeatedly applies the upper-case function to the loop variable's value in v. Since upper-case works on strings but apply expects a list, I've had to use the list function to convert the current value of v (a string) in each iteration to a list.

A better way to do this is to use map:

map applies the named function, upper-case in this example, to each item of the list in turn. The advantage of map is that it both traverses the list and applies the function to each item of the list in one pass. The result is a list, too, which might be more useful for subsequent processing.

There's more about dolist and apply elsewhere (see Working through a list, and Apply and map: applying functions to lists).

reverse
reverse does what you would expect, and reverses the list. It's a destructive function, changing the list permanently.

sort and randomize
In a way, randomize and sort are complementary, although sort changes the original list, whereas randomize returns a disordered copy of the original list. sort arranges the elements in the list into ascending order, organizing them by type as well as by value.

Here's an example: create a list of letters and a list of numbers, stick them together, shuffle the result, and then sort it again:

Compare data before it was randomized and after it was sorted. The sort command sorts the list by data type as well as by value: integers before strings, strings before lists, and so on.

The default sorting method is <, which arranges thet values so that each is less than the next.

To change the sort method, you can supply one of newLISP's built-in comparison functions, such as >. Adjacent objects are considered to be correctly sorted when the comparison function is true for each adjacent pair:

You can supply a custom sort function. This is a function that takes two arguments and returns true if they're in the right order (ie if the first should come before the second), and false if they aren't. For example, suppose you want to sort a list of file names so that the shortest name appears first. Define a function that returns true if the first argument is shorter than the second, and then use sort with this custom sorting function:

("." ".." "var" "usr" "tmp" "etc" "dev" "bin" "sbin" "mach" ".vol" "Users" "cores" "System" "Volumes" "private" "Network" "Library" "mach.sym" ".Trashes" "Developer" "automount" ".DS_Store" "Desktop DF" "Desktop DB" "mach_kernel" "Applications" "System Folder" ...)

Advanced newLISPers often write a nameless function and supply it directly to the sort command:

This does the same job, but saves 25 characters or so. You can use either fn or lambda to define an inline or anonymous function.

unique
unique returns a copy of a list with all duplicates removed:

There are also some useful functions for comparing lists. See Working with two or more lists.

flat
flat is useful for dealing with nested lists, because it shows what they look like without a complicated hierarchical structure:

Fortunately, flat is non-destructive, so you can use it without worrying about losing the structure of nested lists:

transpose
transpose is designed to work with matrices (a special type of list: see Matrices). It also does something useful with ordinary nested lists. If you imagine a list of lists as a table, it flips rows and columns for you:

And here's a nice bit of newLISP sleight of hand:

and each sublist has been reversed. You could, of course, do this:

which is shorter, but a bit slower.

explode
The explode function lets you blow up a list:

You can specify the size of the pieces, too:

List analysis: testing and searching
Often you don't know what's in a list, and you want some forensic tools to find out more about what's inside it. newLISP has a good selection.

We've already met length, which finds the number of elements in a list.

The starts-with and ends-with functions test the start and ends of lists:

These functions work equally well with strings, too (and they take regular expressions). See Testing and comparing strings.

How about contains? In fact there isn't one single newLISP function that does this job. Instead you have find, match, member, ref, filter, index, and count, among others. Which one you use depends on what sort of answer you want to the question Does this list contain this item?, and whether the list is a nested list or a flat one.

If you want a simple answer with a quick top-level-only search, use find. See find.

If you want the item and the rest of the list as well, use member. See member.

If you want the index number of the first occurrence, even if the list contains nested lists, you can use ref. See ref and ref-all.

If you want a new list containing all the elements that match your search element, use find-all. See find-all.

If you want to know whether or not the list contains a certain pattern of elements, use match. See matching patterns in lists.

You can find all list items that satisfy a function, either built-in or one of your own, with the filter, clean, and index functions. See Filtering lists: filter, clean, and index.

The exists and for-all functions check elements in a list to see if they pass a test.

If you want to find elements in a list purely to change them to something else, then don't bother to look for them first, just use replace. See Replacing information: replace. You can also find and replace list elements using the set-ref function. See Find and replace matching elements.

If you want to know how many occurrences there are of items in the list, use count. See Working with two or more lists.

Let's look at some examples of each of these.

find
find looks through a list for an expression and returns an integer or nil. The integer is the index of the first occurrence of the search term in the list. It's possible for find to return 0 - if the list starts with the item, its index number is 0, but this isn't a problem - you can use this function in an if test, because 0 evaluates to true.

No mention of Moriarty

Lestrade is mentioned.

Here I've parsed the text of Sir Arthur Conan Doyle's The Sign of Four (which you can download from Project Gutenberg), and tested whether the resulting list of strings contains various names. The integer returned is the first occurrence of the string element in the list.

find lets you use regular expressions, too, so you can find any string elements in a list that match a string pattern:

Here I'm looking for any traces of chemical indulgence in Sherlock Holmes' bohemian way of life: "(tea|cocaine|morphine|tobacco)" means any one of tea, cocaine, morphine, or tobacco.

This form of find lets you look for regular expression patterns in the string elements of a list. You'll meet regular expressions again when we explore strings. See Regular expressions.

Here we're looking for any string elements in the word-list that match our pattern (a c followed by ie, the old and inaccurate spelling rule i before e except after c).

The regular expression pattern here (enclosed in braces, which are string delimiters, doing more or less the same job as quotation marks) is (c) followed by (ie). Then there's a comment, starting with (?#. Comments in regular expressions are useful when things get cryptic, as they often do.

find can also accept a comparison function. See Searching lists.

find finds only the first occurrence in a list. To find all occurrences, you could keep repeating find until it returns nil. The word list gets shorter each time through, and the found elements are added at the end of another list:

But in this case it's much easier to use filter:

- see Filtering lists: filter, clean, and index.

Alternatively, you can use ref-all (see ref and ref-all) to get a list of indices.

If you're not using regular expressions, you can use count, which, given two lists, looks through the second list and counts how many times each item in the first list occurs. Let's see how many times the main characters' names are mentioned:

The list of results produced by count shows how many times each element in the first list occurs in the second list, so there are 34 mentions of Sherlock, 135 mentions of Holmes, 24 of Watson, and only one mention for poor old Inspector Lestrade in this story.

It's worth knowing that find examines the list only superficially. For example, if the list contains nested lists, you should use ref rather than find, because ref looks inside sublists:

member
The member function returns the rest of the source list rather than index numbers or counts:

matching patterns in lists
There's a powerful and complicated function called match, which looks for patterns in lists. It accepts the wild card characters *, ?, and +, which you use to define a pattern of elements. + means one or more elements, * means zero or more elements, and ? means one element. For example, suppose you want to examine a list of random digits between 0 and 9 for patterns. First, generate a list of 10000 random numbers as source data:

Next, you decide that you want to find the following pattern:

somewhere in the list, ie anything followed by 1 followed by a 2 followed by a 3 followed by anything. Call match like this:

which looks odd, but it's just a pattern specification in a list, followed by the source data. The list pattern:

means any sequence of atoms or expressions (or nothing), followed by a 1, then a 2, then a 3, followed by any number of atoms or expressions or nothing. The answer returned by this match function is another list, consisting of two sublists, one corresponding to the first *, the other corresponding to the second:

and the pattern you were looking for first occurred in the gap between these lists (in fact it occurs half a dozen times later on in the list as well). match can also handle nested lists.

To find all occurrences of a pattern, not just the first, you can use match in a while loop. For example, to find and remove every 0 when it's followed by another 0, repeat the match on a new version of the list until it stops returning a non-nil value:

((2 4) (4 5 4 0 3 6 2 3 0 0 2 0 0 3 3 4 2 0 0 2)) ((2 4 4 5 4 0 3 6 2 3) (2 0 0 3 3 4 2 0 0 2)) ((2 4 4 5 4 0 3 6 2 3 2) (3 3 4 2 0 0 2)) ((2 4 4 5 4 0 3 6 2 3 2 3 3 4 2) (2)) > number-list
 * -> (2 4 4 5 4 0 3 6 2 3 2 3 3 4 2 2)

You don't have to find elements first before replacing them: just use replace, which does the finding and replacing in one operation. And you can use match as a comparison function for searching lists. See Replacing information: replace, and Searching lists.

find-all
find-all is a powerful function with a number of different forms, suitable for searching lists, association lists, and strings. For list searches, you supply four arguments: the search key, the list, an action expression, and the functor, which is the comparison function you want to use for matching the search key:

Here, find-all is looking for the string "onion" in the list food. It's using the > function as a comparison function, so it will find anything that "onion" is greater than. For strings, 'greater than' means appearing later in the default ASCII sorting order, so that "cheese" is greater than "bread" but less than "onion". Notice that, unlike other functions that let you provide comparison functions (namely find, ref, ref-all, replace when used with lists, set-ref, set-ref-all, and sort), the comparison function  must be supplied. With the < function, the result is a list of things that "onion" is less than:

ref and ref-all
The ref function returns the index of the first occurrence of an element in a list. It's particularly suited for use with nested lists, because, unlike find, it looks inside all the sublists, and returns the address of the first appearance of an element. As an example, suppose you've converted an XML file, such as your iTunes library, into a large nested list, using newLISP's built-in XML parser:

Now you can look for any expression in the data, which is in the form of an ordinary newLISP list:

and the returned list will be the location of the first occurrence of that string in the list:

- this is a set of index numbers which together define a kind of address. This example means: in list element 0, look for sublist element 2, then find sublist element 14 of that sublist, and so on, drilling down into the highly-nested XML-based data structure. See Working with XML.

ref-all does a similar job, and returns a list of addresses:

These functions can also accept a comparison function. See Searching lists.

Use these functions when you're searching for something in a nested list. If you want to replace it when you've found it, use the set-ref and set-ref-all functions. See Find and replace matching elements.

Filtering lists: filter, clean, and index
Another way of finding things in lists is to filter the list. Like panning for gold, you can create a filter that keeps only the stuff you want, flushing the unwanted stuff away.

The functions filter and index have the same syntax, but filter returns the list elements, whereas index returns the index numbers (indices) of the wanted elements rather than the list elements themselves. (These functions don't work on nested lists.)

The filtering functions filter, clean, and index use another function for testing elements: the element appears in the results list according to whether it passes the test or not. You can either use a built-in function or define your own. Typically, newLISP functions that tests and return true or false (sometimes called predicate functions) have names ending with question marks:

NaN? array? atom? context? directory? empty? file? float? global? integer? lambda? legal? list? macro? nil? null? number? primitive? protected? quote? string? symbol? true? zero?

So, for example, an easy way to find integers in (and remove floating-point numbers from) a list is to use the integer? function with filter. Only integers pass through this filter:

filter has a complementary function called clean which removes elements that satisfy the test:

Think of clean as getting rid of dirt - it gets rid of anything that passes the test. Think of filter as panning for gold, keeping what passes the test.

This next filter finds all words in Conan Doyle's story The Empty House that contain the letters pp. The filter is a lambda expression (a temporary function without a name) that returns nil if the element doesn't contain pp. The list is a list of string elements generated by parse, which breaks up a string into a list of smaller strings according to a pattern.

You can also use filter or clean for tidying up lists before using them - removing empty strings produced by a parse operation, for example.

When would you use index rather than filter or clean? Well, use index when you later want to access the list elements by index number rather than their values: we'll meet functions for selecting list items by index in the next section. For example, whereas ref found the index of only the first occurrence, you could use index to return the index numbers of every occurrence of an element.

If you have a predicate function that looks for a string in which the letter c is followed by ie, you can use that function to search a list of matching strings:

Remember that lists can contain nested lists, and that some functions won't look inside the sub-lists:

Testing lists
The exists and for-all functions check elements in a list to see if they pass a test.

exists returns either the first element in the list that passes the test, or nil if none of them do.

for-all returns either true or nil. If every list element passes the test, it returns true.

Searching lists
find, ref, ref-all and replace look for items in lists. Usually, you use these functions to find items that equal what you're looking for. However, equality is just the default test: all these functions can accept an optional comparison function that's used instead of a test for equality. This means that you can look for list elements that satisfy any test.

The following example uses the < comparison function. find looks for the first element that compares favourably with n, ie the first element that n is less than. With a value of 1002, the first element that satisfies the test is 1003, the 3rd element of the list, and so the returned value is 3.

You can write your own comparison function:

The longer? function returns true if the first argument is longer than the second. So find, with this function as the comparison, finds the first element in the list that makes the comparison true. Because tiger is longer than dog, the function returns 3, the index of dog in the list.

You could supply an anonymous (or lambda) function as part of the find function, rather than write a separate function:

If you want your code to be readable, you'll probably move longer or more complex comparators out into their own separate - and documented - functions.

You can also use comparison functions with ref, ref-all, and replace.

A comparison function can be any function that takes two values and returns true or false. For example, here's a function that returns true if y is greater than 6 and less than x. The search is therefore for an element of the data list that is both smaller than the searched-for number, which in this case is 15, and yet bigger than 6.

Summary: compare and contrast
To summarize these contains functions, here they are in action:

Selecting items from lists
There are various functions for getting at the information stored in a list:


 * first gets the first element


 * rest gets all but the first element


 * last returns the last element


 * nth gets the nth element


 * select selects certain elements by index


 * slice extracts a sublist

The first and rest functions are more sensible names for the traditional car and cdr LISP functions, which were based on the names of old computer hardware registers.

Picking elements: nth, select, and slice
nth gets the nth element of a list:

nth can also look inside nested lists, because it accepts more than one index number:

If you want to pick a group of elements out of a list, you'll find select useful. You can use it in two different forms. The first form lets you supply a sequence of loose index numbers:

A positive number selects an element by counting forward from the beginning, and a negative number selects by counting backwards from the end:

0     1       2      3       4      5      6      7     8 ("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog") -9    -8      -7     -6      -5     -4     -3     -2    -1

You can also supply a list of index numbers to select. For example, you can use the rand function to generate a list of 20 random numbers between 0 and 8, and then use this list to select elements from phrase at random:

Notice the duplicates. If you had written this instead:

there would be no duplicates: (randomize phrase) shuffles elements without duplicating them.

slice lets you extract sections of a list. Supply it with the list, followed by one or two numbers. The first number is the start location. If you miss out the second number, the rest of the list is returned. The second number, if positive, is the number of elements to return.

If negative, the second number specifies an element at the other end of the slice counting backwards from the end of the list, -1 being the final element:

The cake knife reaches as far as - but doesn't include - the element you specify.

Implicit addressing
newLISP provides a faster and more efficient way of selecting and slicing lists. Instead of using a function, you can use index numbers and lists together. This technique is called implicit addressing.

Select elements using implicit addressing
As an alternative to using nth, put the list's symbol and an index number in a list, like this:

If you have a nested list, you can supply a sequence of index numbers that identify the list in the hierarchy:

where the '(2 1) first finds element 2, ("lion" 4), then finds element 1 (the second one) of that sublist.

Selecting a slice using implicit addressing
You can also use implicit addressing to get a slice of a list. This time, put one or two numbers to define the slice, before the list's symbol, inside a list:

Earlier, we parsed the iTunes XML library:

Let's access the inside of the resulting XML structure using the implicit addressing techniques:

How to remember the difference between the two types of implicit addressing? sLice numbers go in the Lead, sElect numbers go at the End.

Shortening lists
To shorten a list, by removing elements from the front or back, use chop or pop. chop makes a copy and works from the end, pop changes the original and works from the front.

chop returns a new list by cutting the end off the list:

An optional third argument for chop specifies how many elements to remove:

pop (the opposite of push) permanently removes the specified element from the list, and works with list indices rather than lengths:

You can also use replace to remove items from lists.

Changing items in lists
You can easily change elements in lists, using the following functions:


 * replace changes or removes elements


 * swap swaps two elements


 * setf sets the value of an element


 * set-ref searches a nested list and changes an element


 * set-ref-all searches for and changes every element in a nested list

These are destructive functions, just like push, pop, reverse, and sort, and change the original lists, so use them carefully.

Changing the nth element
To set the nth element of a list (or array) to another value, use the versatile setf command:

Notice how the setf function returns the value that has just been set, 0, rather than the changed list.

This example uses the faster implicit addressing. You could of course use nth to create a reference to the nth element first:

setf must be used on a list or array or element stored in a symbol. You can't pass raw data to it:

Using it
Sometimes when you use setf, you want to refer to the old value when setting the new value. To do this, use the system variable $it. During a setf expression, $it contains the old value. So to increase the value of the first element of a list by 1:

You can do this with strings too. Here's how to 'increment' the first letter of a string:

Replacing information: replace
You can use replace to change or remove elements in lists. Specify the element to change and the list to search in, and also a replacement if there is one.

Every matching item is deleted.

replace returns the changed list:

The replacement can be a simple value, or any expression that returns a value.

replace updates a set of system variables $0, $1, $2, up to $15, and the special variable $it, with the matching data. For list replacements, only $0 and $it are used, and they hold the value of the found item, suitable for using in the replacement expression.

For more about system variables and their use with string replacements, see System variables.

If you don't supply a test function, = is used:

You can make replace find elements that pass a different test, other than equality. Supply the test function after the replacement value:

The test can be any function that compares two values and returns a true or false value. This can be amazingly powerful. Suppose you have a list of names and their scores:

How easy is it to add up the numbers for all those people whose scores included a 0? Well, with the help of the match function, this easy:

Here, for each matching element, the replacement expression builds a list from the name and the sum of the scores. match is employed as a comparator function - only matching list elements are selected for totalization, so Eric's scores weren't totalled since he didn't manage to score 0.

See Changing substrings for more information about using replace on strings.

Modifying lists
There are even more powerful ways of modifying the elements in lists. Meet set-ref and set-ref-all.

You can locate and modify elements using these functions, which are designed to work well with nested lists. (See also Working with XML for some applications.)

Find and replace matching elements
The set-ref function lets you modify the first matching element in a list:

To change that 200 to a 300, use set-ref like this:

Find and replace all matching elements: set-ref-all
set-ref finds the first matching element in a nested list and changes it; set-ref-all can replace every matching element. Consider the following nested list that contains data on the planets:

How could you change every occurrence of that 'incline' symbol to be 'inclination'? It's easy using set-ref-all:

This returns the list with every 'incline changed to 'inclination.

As with replace, the default test for finding matching elements is equality. But you can supply a different comparison function. This is how you could examine the list of planets and change every entry where the moon's value is greater than 9 to say "lots" instead of the actual number.

The replacement expression compares the number of moons (the last item of the result which is stored in $0) and evaluates to "lots" if it's greater than 9. The search term is formulated using match-friendly wildcard syntax, to match the choice of comparison function.

Swap
The swap function can exchange two elements of a list, or the values of two symbols. This changes the original list:

Usefully, swap can also swap the values of two symbols without you having to use an intermediate temporary variable.

This parallel assignment can make life easier sometimes, such as in this slightly unusual iterative version of a function to find Fibonacci numbers:

Working with two or more lists
If you have two lists, you might want to ask questions such as How many items are in both lists?, Which items are in only one of the lists?, How often do the items in this list occur in another list?, and so on. Here are some useful functions for answering these questions:


 * difference finds the set difference of two lists


 * intersect finds the intersection of two lists


 * count counts the number of times each element in one list occurs in a second list

For example, to see how many vowels there are in a sentence, put the known vowels in one list, and the sentence in another (first use explode to turn the sentence into a list of characters):

or the slightly shorter:

The result, (1 4 1 4 2), means that there are 1 a, 4 e's, 1 i, 4 o's, and 2 u's in the sentence.

difference and intersect are functions that will remind you of those Venn diagrams you did at school (if you went to a school that taught them). In newLISP lists can represent sets.

difference returns a list of those elements in the first list that are not in the second list. For example, you could compare two directories on your system to find files that are in one but not the other. You can use the directory function for this.

It's important which list you put first! There are five files or directories in directory d1 that aren't in directory d2, but there are no files or directories in d2 that aren't also in d1.

The intersect function finds the elements that are in both lists.

Both these functions can take an additional argument, which controls whether to keep or discard any duplicate items.

You could use the difference function to compare two revisions of a text file. Use parse (Parsing strings) to split the files into lines first:

Association lists
There are various techniques available in newLISP for storing information. One very easy and effective technique is to use a list of sublists, where the first element of each sublist is a key. This structure is known as an association list, but you could also think of it as a dictionary, since you look up information in the list by first looking for the key element.

You can also implement a dictionary using newLISP's contexts. See Introducing contexts.

You can make an association list using basic list functions. For example, you can supply a hand-crafted quoted list:

Or you could use functions like list and push to build the association list:

It's a list of sublists, and each sublist has the same format. The first element of a sublist is the key. The key can be a string, a number, or a symbol. You can have any number of data elements after the key.

Here's an association list that contains some data about the planets in the solar system:

Each sublist starts with a string, the name of a planet, which is followed by data elements, numbers in this case. The planet name is the key. I've included some comments at the end, because I'm never going to remember that element 2 is the planet's mass, in Earth masses.

You could easily access this information using standard list-processing techniques, but newLISP offers some tailor-made functions that are designed to work specifically with these dictionary or association lists:


 * assoc finds the first occurrence of the keyword and return the sublist


 * lookup looks up the value of a keyword inside the sublist

Both assoc and lookup take the first element of the sublist, the key, and retrieve some data from the appropriate sublist. Here's assoc in action, returning the sublist:

And here's lookup, which goes the extra mile and gets data out of an element of one of the sublists for you, or the final element if you don't specify one:

This saves you having to use a combination of assoc and nth.

One problem that you might have when working with association lists with long sublists like this is that you can't remember what the index numbers represent. Here's one solution:

Here we've defined orbital-radius and au (astronomical unit) as constants, and you can use orbital-radius to refer to the right column of a sublist. This also makes the code easier to read. The constant function is like set, but the symbol you supply is protected against accidental change by another use of set. You can change the value of the symbol only by using the constant function again.

Having defined these constants, here's an expression that lists the different orbits of the planets, in kilometres:

Mercury         0.39           57894426 Venus           0.72          107710560 Earth           1.00          149598000 Mars            1.52          227388960 Jupiter         5.20          777909600 Saturn          9.54         1427164920 Uranus         19.22         2875273560 Neptune        30.06         4496915880 Pluto          39.50         5909121000

When you want to manipulate floating-point numbers, use the floating-point arithmetic operators add, sub, mul, div rather than +, -, *, and /, which work with (and convert values to) integers.

Replacing sublists in association lists
To change values stored in an association list, use the assoc function as before, to find the matching sublist, then use setf on that sublist to change the value to a new sublist.

Adding new items to association lists
Association lists are ordinary lists, too, so you can use all the familiar newLISP techniques with them. Want to add a new 10th planet to our sol-sys list? Just use push:

and check that it was added OK with:

You can use sort to sort the association list. (Remember though that sort changes lists permanently.) Here's a list of planets sorted by mass. Since you don't want to sort them by name, you use a custom sort (see sort and randomize) to compare the mass (index 2) values of each pair:

You can also easily combine the data in the association list with other lists:

&#x263f; (Unicode symbol for Mercury) &#x2640; (Unicode symbol for Venus) &#x2641; (Unicode symbol for Earth) &#x2642; (Unicode symbol for Mars) &#x2643; (Unicode symbol for Jupiter) &#x2644; (Unicode symbol for Saturn) &#x2645; (Unicode symbol for Uranus) &#x2646; (Unicode symbol for Neptune) &#x2647; (Unicode symbol for Pluto)

Here we've created a temporary inline function that map applies to each planet in sol-sys - lookup finds the planet name and retrieves the Unicode symbol for that planet from the unicode-symbols association list.

You can quickly remove an element from an association list with pop-assoc.

This removes the Pluto element from the list.

newLISP offers powerful data storage facilities in the form of contexts, which you can use for building dictionaries, hash tables, objects, and so on. You can use association lists to build dictionaries, and work with the contents of dictionaries using association list functions. See Introducing contexts.

You can also use a database engine - see Using a SQLite database.

find-all and association lists
Another form of find-all lets you search an association list for a sublist that matches a pattern. You can specify the pattern with wildcard characters. For example, here's an association list:

To find all the sublists that end with 9, use the match pattern (? 9), where the question mark matches any single item:

(For more about match patterns - wild card searches for lists - see matching patterns in lists.)

You can also use this form with an additional action expression after the association list:

Here, the action expression uses $0 to refer to each matched element in turn.