Harry's Code Class: Tips for Programmers (July 2007)23 Jul, 2007
Lists: Building Blocks of LISP
Put the parentheses to work and accelerate AutoCAD productivity.
The LISP programming language was created with list manipulations in mind. Although you can define brilliant programs and applications without incorporating lists, it's almost impossible to avoid working with lists all together. In the Visual LISP system, points are lists, and AutoCAD drawing data is often supplied using lists. Thus, to truly master Visual LISP and AutoCAD customization, you must become comfortable with list manipulation. In this newsletter,
Note: Beginners will need to read this newsletter a few times and look at further references to get a good grasp of the topic. More experienced LISP programmers will find this to be a good review of the basics of list manipulation.
The Core of LISP
The good news is that a list is an easy concept to understand. Most of us use lists every day -- "to do" lists, instructions, and so forth. Some lists, such as driving directions, must be presented and followed in a specific order. Other lists, such as shopping lists, can be randomly written and processed. Simple as they are in concept, lists are an essential part of life.
The concept of list manipulation was built into the original design of the LISP programming language. LISP stands for List Processing -- not Lost In Stupid Parentheses, although that is also pretty accurate. In the LISP language, anything surrounded by parentheses is a list -- including both data and programs -- a very unique feature of the LISP language. If a portion of code is not a list, then it is an atom. Lists are composed of atoms and other lists.
Lists are powerful data storage tools when working with AutoCAD. Points are lists of two or three numbers (the x, y, and possibly z ordinates). Graphics objects are defined by points and may contain multiple points such as a line made up two end points. That means a line can be defined as a list containing lists of points as well as other data, such as color, layer, line type, and so forth.
When working with lists, you are often working with one of the fundamentals of the programming language. Examples are the CAR and CADR internal functions, which are commonly used to extract the x,y coordinates of a point list. CAR and CADR have various applications, but in this instance, they simply reference the first and second members of a list, which in the case of a point list happen to be x,y.
CAR and CADR are part of a set of functions known as composite primitives. They are considered a composite because you combine as many as four A and D characters in various ways between the C and R to navigate around a list. The character A indicates the head or start of a list, while D returns the remainder of a list. The A/D sequence is processed in reverse. Thus, CADR means to get the remainder of the list, then return to the head of that list. When the list contains an x,y coordinate, CADR returns the y value.
- (setq PT '(1.0 2.5)) creates the point list (1.0 2.5)
- (car PT) returns 1.0
- (cadr PT) returns 2.5
Composite primitives are useful when working with those elements near the start or head of a list. When you need to access deep into a list, you can use NTH. As the name implies, NTH will return the "nth" numbered member of a list. There is just one little catch you need to remember: The list numbering starts at zero. Another way to think about it is that the index value used is in reality an offset into the list from the start. The first element is offset zero from the head; the second is offset by one, and so forth to the end of the list, which is offset one less than the length of the list.
- (setq Data '(10 20 30 40 50 60 70 80)) creates a list of numbers
- (nth 1 Data) returns 20
- (nth 0 Data) returns 10
- (nth 5 Data) returns 60
Build That List
Using NTH and the composite primitives will allow you to navigate through any list structure. The next thing to consider is how to build up a list. The simplest method is to use the quote mark, as shown in the previous examples, or the LIST subr (subroutine) to define a list of data. Deciding between the two options is relatively simple: If you have data that must be evaluated before it is placed in the list, use LIST. If you have constants only, use the quote.
- (setq A 1.5)
- (setq DataList '(A 2.5 3.5)) creates the list (A 2.5 3.5).
The first member of the list is a symbol because the quote tells LISP not to evaluate the symbol into a value. If you want the value, use LIST, as in the following:
- (setq DataList (list A 2.5 3.5)) creates the list (1.5 2.5 3.5).
Because we used the LIST subr, the symbol A was evaluated to the value of 1.5, which ended up in the actual data list. Quotes stifle the evaluation of symbols -- be aware of this when creating lists.
You can add data to an existing list using CONS and APPEND. CONS will add data to the front of a list; APPEND is used to run lists of data together into a single list. Although using APPEND might seem to make the most sense for a beginner, most seasoned LISP programmers prefer to use CONS to build lists from back to front. Why? CONS executes faster and requires less overhead than APPEND. A lot of Visual LISP follows this same concept. For example, the SSADD subr adds elements to the front of a selection set.
- (setq DataList '(2 3 4)) creates the list (2 3 4)
(setq DataList (cons 1.5 DataList)) changes DataList to
(1.5 2 3 4)
- (setq DataList (append DataList (list 5 6))) changes DataList to (1.5 2 3 4 5 6)
Whether a list is constructed backward or forward is important only when you go to process the list. Should you need to change the order, you can always use the REVERSE subr.
- (setq DataList (reverse DataList)) changes DataList to
(6 5 4 3 2 1.5)
At this point, if you know other programming languages, you should be visualizing lists as arrays. And that makes perfect sense -- except that lists are a lot more flexible and therefore powerful by comparison. Arrays are typically well structured and of the same data type. Lists can be any structure of balancing parentheses and any data type. LISP contains tools for manipulating lists that you would not normally find in other programming languages.
Lists of data are often processed using a programming structure known as a loop. A loop is a section of coding that repeats. Loops are very useful when processing lists of data because you can repeat the same instructions for each member of the list on each pass through the loop. LISP provides a few looping options for lists. Pick an option according to which type of data you want to pass directly back to your program when the loop is finished: a list of results, a single result, or no result.
No result. No result is the option of choice when your objective is to do something with the list data such as generate a new polyline given a list of data points. In those cases, the FOREACH subr may be the best solution because it will return the result of the last expression in the loop. Rarely is this result saved; hence, it can be thought of as producing no result.
Single result. Getting a single result is used when you want to apply an operation to the results of a loop through a list of data, such as when summing numbers. The APPLY subr is designed for such circumstances. (APPLY
'+ '(2 3 4)) returns 9. Note that a quote is used when defining the action to take place. The quote tells LISP not to evaluate but use the symbol. In this situation the plus symbol is passed to the APPLY subr to be used with the data list that follows. If you need to do more than a simple operation with each of the values, you can define a function that accepts a single argument. The argument value passed in to the function will be the values from the data list. Another option is to define an anonymous function in place using the LAMBDA subr.
List of results. Finally, you'll want to get a list of results when you wish to preserve the result from each of the loop iterations, such as changing a list of points by adding some value to each coordinate. The MAPCAR subr will produce a list of results. Like APPLY, MAPCAR uses a quoted operation followed by the data list. If the operation is extensive, you can define a function and just provide the quoted function name. Otherwise, LAMBDA can be used to create a simple anonymous function.
In the following example set, we start by defining a list of data points.(setq PTS '((10 20) (10 30) (20 30) (20 10)))
PTS is a list of four point lists. The two open parentheses after the quote define a nested list structure -- a list within a list. Now we want to add 5 to all the x coordinate values in the list using the following expression.
(setq PTS (mapcar '(lambda (P) (list (+ (car P) 5) (cadr P))) PTS))
Although cryptic on first look, this expression demonstrates the innate power of list processing in LISP. The data list PTS found at the end of the expression is the input list we just defined. Each value inside PTS will be extracted and passed to the anonymous LAMBDA function as P. Note that the LAMBDA expression begins with a quote. Inside the LAMBDA function we build a list by taking the CAR or x value from P + 5 along with the CADR or y value from P. That is all LAMBDA does, but the result is that the list is now ((15 20) (15 30) (25 30)
Creating a polyline with these points is easy using the FOREACH and COMMAND subrs:
(foreach P PTS (command P))
The first line starts the Polyline (Pline) command. The second line is the FOREACH loop, and each point list in PTS is placed in P. That value is passed to AutoCAD's Pline command, which is already running. When all the points in PTS have been processed, the third line runs, ending the Pline command.
All this is just an introduction to list manipulations in Visual LISP. Use the SUBR names and the Visual LISP Help file to learn more and see more examples of these powerful tools in action. Mastering LISP involves mastering list processing, and you'll be amazed that with just a little practice it becomes easy to think and program using lists. Because LISP allows you build lists of any structure and at any time in your programs, your imagination can run wild. And that is where the real fun begins!
Until next time, keep on programmin'!