AutoLISP Lesson 9 - DATA STRUCTURES
(April 1999) All data structures in AutoLISP are list's. Everything in AutoLISP is a list, if it's not a function, it's a list. If it's a list, it's a data structure.
A list is nothing more than a single dimension array. If you have a list of lists, then you have a two dimensional arrays. It is even possible to nest two-dimensional lists, inside another two dimensional list. I'm not sure what you would call that, but I'd call it a mess. If you nest your lists too deeply, things can get out of hand. As you may have guessed, AutoLISP has many predefined functions for working with lists.
append (append exp ...) (append '(1 2) '(3 4)) ;Returns (1 2 3 4)
Takes any number of lists and combines them into one list.
apply (apply exp list) (apply '+ '(1 2 3) ;Returns 6
Executes the function "exp" using the items in the list as parameters to the function.
assoc (assoc item alist) (assoc 1 '((1 "David")(2 "Allen")(3 "Seibert))) ;Returns (1 "David")
Searches an association list using "item" as the key, returns the associated entry.
car (car list) (car '(1 2 3 4)) ;Returns 1
Returns the first item in the list.
cdr (cdr list) (cdr '(1 2 3 4)) ;Returns (2 3 4)
Return a list containing all but the first item in the list.
cons (cons item list) (cons 0 '(1 2 3)) ;Returns (0 1 2 3 4)
Returns a list with "item" as the new first item in the list.
foreach (foreach name list exp) (foreach n '(1 2 3 4) (+ n 1)) ;Returns (2 3 4 5)
Steps through a list and evaluates each expression for every item in the list.
last (last exp ...) (last 1 2 3 4) ;Returns 4
Returns the last element in a list
list (list exp ...) (list 1 2 3 4) ;Returns (1 2 3 4)
Creates a list from a number of expressions.
mapcar (mapcar function list1 ... listn) (mapcar '* '(1 2 3) '(4 5 6) ;Returns (4 10 18)
Returns a list as the result of executing a function with the items of the lists supplied.
member (member exp list) (member 2 '(1 2 3)) ;Returns (2 3)
Searches a list for an occurrence of an expression and returns the remainder of the list starting with the first occurrence of the expression.
nth (nth n list) (nth 2 '(1 2 3 4)) ;Returns 2
Returns the "n"th item from "list".
reverse (reverse list) (reverse '(1 2 3)) ;Returns (3 2 1)
Returns the list with it's items reversed.
subst (subst newitem olditem list) (subst 5 2 '(1 2 3 4)) ;Returns (1 5 3 4)
Returns a list with "newitem" in place of every occurrence of "olditem".
As you see, just about anything you could want to do to a list, can be done using the predefined function for list handling.
An AutoLISP list, is nothing less than a single dimensional array in any other language. When we start on lists of lists, I will be referring to them as arrays. For now let’s just learn how to deal with a list.
Creating single-dimensional lists
The CONS, APPEND, and LIST functions are the basic list creating functions. Combining any of these functions with the setq function can create a list. The QUOTE function may also be used to create a list as you will see in the examples to follow.
The LIST and QUOTE functions are used when you want to create a static list of items. That is, they create lists of items when you know what you want the lists to contain. The "list" function may also be combined with other lisp functions, to create dynamic lists, but not too often.
(setq list_name (list 1 2 3)) ;Returns (1 2 3)
If we now check the binding of list_name, we will find it is bound to the list (1 2 3). A list is the basic storage device of AutoLISP. Every structure we will look at is nothing more than a list, and a list is nothing more than a single dimensioned array. Using the quote function could also have created the list above.
(setq list_name (quote (1 2 3))) (setq list_name '(1 2 3))
Either of the quoting mechanisms will due. This is probably the easiest way to create a list when you know ahead of time what you want your list to contain.
The CONS and APPEND functions are used to create dynamic lists. That is, they create list where we don't know ahead of time what the list will contain, and we kind of build the list "on the fly". The CONS command is the basic list constructor, it creates lists by adding items to the front of the list. You must give CONS the item to add to the list, and the list you want the item added to.
(setq list_name (cons item list_name))
The listing above shows how to use the CONS function to create a list. The APPEND function will take any number of lists and combine them into one list. The thing to remember with the APPEND function is the items passed to it must be lists.
(setq list_name (append '(1 2 3) '(4 5 6))) ;Returns (1 2 3 4 5 6)
As you should be able to see by now, creating lists is a semi-straight forward operation. Next we will learn how to access lists, which is just as easy.
Accessing single dimensional lists
To pull values from a list there are several functions we can use. CAR and CDR are two of the more popular ways of getting data from a list. If we use the CAR function it will retrieve the first item from the list.
(car '(4 5 6)) ;Returns 4
CDR will retrieve the list starting with the second item and continuing to the end.
(cdr '(4 5 6)) ;Returns (5 6)
These two functions may be nested with each other up to four levels deep. What that means is if you want just the second item from the list, you can combine these two function to just return the second item. We will CDR then CAR the list. This gives us the list from the second item on, and then gets the first item from the new list.
(cadr '(4 5 6)) ;Returns 5
If we wanted to get the third item from the list we can do this also. We just combine CDR CDR CAR to get to the third item.
(caddr '(4 5 6)) ;Returns 6
When you first look at this way of retrieving data from a list, it may seem like the long way around, but there are times when these functions can be quite useful. All you have to do to see how useful they are, is to remember that a point is a list of three values for X, Y, and Z. Remember these functions later when we discuss associated lists. The LAST function can be used to retrieve the last item from a list. If you know that the last item in the list is the one you want, this is the function to use.
The MEMEBER function tells you whether an item is found in a list. You will usually use this prior to the SUBST function. Before you can replace an item in a list you have to know whether it is a member of the list. The MEMBER function will tell you if an item is a member of a given list. The NTH function for retrieving data from a list at a specified location. The nth function is handy when you know exactly where you are going in the list.
(nth 3 '(2 3 4 5 6)) ;Returns 4
That was pretty easy. The NTH function is normally used with an index to walk through a list one step at a time. When we are searching a list for some value, we will set up an index and use the NTH function, along with an incrementer for the index to step through the list.
(while ;While (and (nth Ndx list_name) ;there are still ;items in the list (/= (nth Ndx list_name) search_item));and they aren't what ;we're looking for (= Ndx (+ Ndx 1))) ;increment the index
The previous bit of code will let us look through the list until one of two things occurs. Either we find what we're looking for or we run out of items. The only thing you need to do before running the code above is to initialize your Ndx, list_name, and the search_item.
If the list you are dealing with is an "association" list, you will need to use the ASSOC function to access this list. The ASSOC function searches a list for a key, and return whatever list is found to contain that key. If you wanted to store variables and their values in a list, you would probably use an association list to store them.
The association list is set up like in the following example.
(setq list_name (list (1 "This") (2 "is") (3 "an") (4 "association") (5 "list")))
You can now key on the numbers to get the data from the list. Most of the time you will be using the "cadr" function to access the data in the list. If you didn't, the following examples will show you what happens.
(assoc 1 list_name) ;Returns (1 "This") (assoc 4 list_name) ;Returns (4 "association") (cadr (assoc 2 list_name)) ;Returns "is" (cadr (assoc 5 list_name)) ;Returns "list"
As you can see the ASSOC function by itself is not so useful but if you combine it with the CADR function you can get to any data you need.
Modifying single dimensional lists
The APPLY function executes a function using the lists as the arguments to the function. It's useful for totaling numbers you have saved in a list.
(apply '+ (23 12 -10)) ;Returns 25
APPLY can be used with any function, and will pass the items in the list, starting from left to right, as the parameters to the function. If the function you are using takes one parameter then one item from the list at a time will be passed. If the function needs two parameters then two items from the list will be passed.
The FOREACH function is used to make mass changes to a list. It works on every item in the list and makes the same change to each item. Let's say we collected some user input in a list, and we want to check the answers the user gave, with some constants. The first thing we want to do is convert all the answer to uppercase so all we have to do is check uppercase strings. This is how we would set up the function to convert to uppercase.
(foreach n ans_list (strcase n))
This will return a list with every string in the list converted to uppercase.
The next function I will talk about for getting inside your lists, is the SUBST function. The SUBST function is used to modify the data in your lists. The SUBST function does just what it sounds like, it substitutes one data item in the list for another. The explanation given in the function definitions above should be enough to get you going with SUBST.
Using single-dimensional lists
The MAPCAR function will pass the items from two or more lists as the parameters for a function. MAPCAR returns a new list as the result of passing the old lists to the function. This is handy for creating new point lists from lists containing the "x" and "y" coordinates.
(setq point_list (mapcar 'list x_list y_list))
We can use the same functions for creating arrays that are used for lists, because an array is nothing more than a list. It's sometimes easier to think of things in the context of an array than to try to see them as lists. A list tends to be a long string of items, where an array is a more structured item.
The quote symbol will be used for laying out arrays when we know what we are putting in the array. An array of constant data is such an array, and the listing below shows how to layout such an array. This is a list of the "tap" diameter and the "tap drill" diameters for some popular screw sizes.
(setq BigTapList '( (("Name" "#3") ("Tap" 0.099) ("TDrill" 0.0785)) (("Name" "#4") ("Tap" 0.112) ("TDrill" 0.0890)) (("Name" "#5") ("Tap" 0.125) ("TDrill" 0.1015)) (("Name" "#6") ("Tap" 0.138) ("TDrill" 0.1065)) (("Name" "#8") ("Tap" 0.164) ("TDrill" 0.1360)) (("Name" "#10") ("Tap" 0.1875) ("TDrill" 0.1495)) (("Name" "1/4") ("Tap" 0.2500) ("TDrill" 0.2010)) (("Name" "5/16")("Tap" 0.3125) ("TDrill" 0.2570)) (("Name" "3/8") ("Tap" 0.375) ("TDrill" 0.3125)) (("Name" "1/2") ("Tap" 0.500) ("TDrill" 0.4219)) (("Name" "5/8") ("Tap" 0.625) ("TDrill" 0.5469)) (("Name" "3/4") ("Tap" 0.750) ("TDrill" 0.6563)) (("Name" "M3") ("Tap" 0.11811024) ("TDrill" 0.09842520)) (("Name" "M4") ("Tap" 0.15748032) ("TDrill" 0.12992126)) (("Name" "M5") ("Tap" 0.19685039) ("TDrill" 0.16535433)) (("Name" "M6") ("Tap" 0.23622047) ("TDrill" 0.19685039)) (("Name" "M8") ("Tap" 0.31496063) ("TDrill" 0.26574803)) (("Name" "M10") ("Tap" 0.39370079) ("TDrill" 0.33464567)) (("Name" "M12") ("Tap" 0.47244094) ("TDrill" 0.40354331)) (("Name" "M16") ("Tap" 0.62992126) ("TDrill" 0.55118110)) (("Name" "M20") ("Tap" 0.78740157) ("TDrill" 0.68897638))))
Laying the previous list out in the form of an array makes the list easier to see, and deal with. Had I put each item in the list end to end, it would have been extremely difficult to see what was in the list. The layout above allows me to see every list in the array, and when I'm writing my access routines I will be able to tell how deep into the array I have to dig.
To make a complete function that would return the list to my calling function I would add the following code. By getting my list of drill sizes like this, I don't need to have the whole BigTapList in memory for the entire duration of my program. I can just get the data I need and let the rest go away with the function to retrieve the data.
(defun get_tap_list ( TapSize / BigTapList TapNdx TapList ) (setq BigTapList ;code shown above ;goes here ) (setq TapNdx 0 TapList (car BigTapList)) (while (/= TapSize (cadr (assoc "Name" TapList))) (setq TapList (nth TapNdx BigTapList)) (setq TapNdx (+ TapNdx 1))) (car (list TapList)) )
By doing things the ways I have shown above we keep only that data we need. Any data that will not be used by your program should not exist. Memory is large and cheap these days, but we still need to use good programming practices so we don't get caught off-guard by anything.
Featured blocks of the month