Mapcar & Lambda

Auto CAD/lisp|2021. 2. 20. 10:02

In my experience, the mapcar and lambda functions are two of the least understood functions in the AutoLISP programming language, however, when understood and used correctly, they can replace superfluous code and are powerful functions for dealing with lists.

The Mapcar Function

Put simply, mapcar will evaluate a function on every element of one or more lists and return a list of the result of each such evaluation.

The mapcar function is used in the following way:

(mapcar <function> <list-1> <list-2> ... <list-n>)

Where function is a function to be evaluated on each item in the supplied list(s); this can be any function, user-defined or otherwise, taking any number of arguments.

list-1 ... list-n are a number of lists equal to the number of arguments required by the function to be evaluated. Hence if used with a function requiring a single argument, such as strcase (without the case flag), only one list need be supplied.

Let me clarify this explanation with an example.

Example 1

Assume we have the following list of strings:

("adam" "ben" "claire" "david")

Suppose we wish to convert each of these strings into uppercase (capitals).

We could approach this task in a number of ways, for example, using the foreach function to shuffle through the list and create a new list of uppercase strings:

(foreach str (reverse '("adam" "ben" "claire" "david"))
    (setq lst (cons (strcase str) lst))
)

The resultant value of the lst variable would then be:

_$ lst
("ADAM" "BEN" "CLAIRE" "DAVID")

However this same task can be accomplished with more concision using the mapcar function:

_$ (mapcar 'strcase '("adam" "ben" "claire" "david"))
("ADAM" "BEN" "CLAIRE" "DAVID")

Since the strcase function requires a single argument (a string) when converting to uppercase, only one list is supplied: the list of strings to be converted to uppercase.

The result: mapcar evaluates the strcase function on each element of the supplied list - this is equivalent to:

_$ (list (strcase "adam") (strcase "ben") (strcase "claire") (strcase "david"))
("ADAM" "BEN" "CLAIRE" "DAVID")

Notice also that mapcar has been supplied with the function strcase prefixed with an apostrophe so that the strcase symbol is not evaluated, but rather treated as an argument for the mapcar function. This could also be achieved using the quote function in the following way:

_$ (mapcar (quote strcase) (quote ("adam" "ben" "claire" "david")))
("ADAM" "BEN" "CLAIRE" "DAVID")

Or using the function function to declare strcase as a function:

_$ (mapcar (function strcase) (quote ("adam" "ben" "claire" "david")))
("ADAM" "BEN" "CLAIRE" "DAVID")

Functions with More than One Argument

In the example above, the strcase function is demonstrated, and only one list is supplied as strcase only requires a single argument when converting to uppercase; but what if we want to use a function that requires multiple arguments?

Example 2

Consider the following example to add the items of two lists:

_$ (mapcar '+ '(1 2 3 4 5) '(3 4 5 6 7))
(4 6 8 10 12)

Here mapcar is supplied with the + function, which takes any number of numerical arguments and adds them together. Hence in the above example, mapcar will evaluate the + function on each member of each list and return a list of the results:

(4 6 8 10 12) = ((+ 1 3) (+ 2 4) (+ 3 5) (+ 4 6) (+ 5 7))

If the supplied lists are unequal in length, mapcar will cease evaluation when the shortest list has been processed, e.g.:

_$ (mapcar '+ '(1 2 3 4 5) '(3 4 5))
(4 6 8)

The Lambda Function

Let's go back to our original list of strings:

("adam" "ben" "claire" "david")

Suppose that we wish to convert each string to 'Titlecase' such that the first letter of each word is capitalised, for example:

("Adam" "Ben" "Claire" "David")

We could again implement a method using the foreach function, and shuffle through our list:

(foreach str (reverse '("adam" "ben" "claire" "david"))
    (setq lst (cons (strcat (strcase (substr str 1 1)) (substr str 2)) lst))
)

But we could also use mapcar to avoid the need to reverse the list and involve the extra variables. However, there is no function in LISP that allows us to capitalise the first letter of a word, so how to can we provide mapcar with a function to evaluate?

Since the function supplied to mapcar is arbitrary, one possible solution could be to define a function ourselves and supply it:

(defun titlecase ( str )
    (strcat (strcase (substr str 1 1)) (substr str 2))
)

_$ (mapcar 'titlecase '("adam" "ben" "claire" "david"))
("Adam" "Ben" "Claire" "David")

Here we have defined a function titlecase which takes a single argument str, and returns the value of this argument with the first letter capitalised.

But this means that we now have an extra function definition in our code that may only be used once and could potentially be located elsewhere in the code, making it far less readable. A far better way to accomplish this task would be to use the lambda function.

The lambda function defines an anonymous function (a defun without a name if you like) and is usually used when the overhead of defining a new function is not justified - as in our case.

(mapcar '(lambda ( s ) (strcat (strcase (substr s 1 1)) (substr s 2))) '("adam" "ben" "claire" "david"))

The code is now far more comprehensible since the operations performed by the anonymous lambda function are in-line with the flow of the code.

Further Examples

The mapcar and lambda functions are best illustrated and understood using examples, so here are a few more for you to study:

Example 3

Adding one to each element of a list:

_$ (mapcar '1+ '(1 2 3 4 5))
(2 3 4 5 6)

Example 4

Adding two to each element of a list, using a defined function:

(defun addtwo ( x ) (+ x 2))

_$ (mapcar 'addtwo '(1 2 3 4 5))
(3 4 5 6 7)

Using an anonymous lambda function:

_$ (mapcar '(lambda ( x ) (+ x 2)) '(1 2 3 4 5))
(3 4 5 6 7)

Example 5

Concatenating a string with an integer, using a defined function taking two arguments:

(defun join ( str int ) (strcat str (itoa int)))

_$ (mapcar 'join '("a" "b" "c") '(1 2 3))
("a1" "b2" "c3")

Using an anonymous lambda function taking two arguments:

_$ (mapcar '(lambda ( str int ) (strcat str (itoa int))) '("a" "b" "c") '(1 2 3))
("a1" "b2" "c3")

Example 6

One final example to scramble the brain: a function to return the average point of a list of points:

(defun averagepoint ( lst / n )
    (setq n (float (length lst)))
    (mapcar '/ (apply 'mapcar (cons '+ lst)) (list n n n))
)

Notice that the above function exploits the fact that mapcar will cease evaluation when the shortest list is exhausted, since calling the function with 3D points will result in a 3D average point, and supplying 2D points will result in a 2D average point:

_$ (averagepoint '((2 3 1) (2 4 1) (1 5 1)))
(1.66667 4.0 1.0)

_$ (averagepoint '((2 3) (2 1) (5 1)))
(3.0 1.66667)

Observe that this behaviour could also be obtained by using a lambda function to perform the division:

(defun averagepoint ( lst / n )
    (setq n (float (length lst)))
    (mapcar (function (lambda ( x ) (/ x n))) (apply 'mapcar (cons '+ lst)))
)

Further Reading

If this tutorial has sparked your interest in all things mapcar and lambda, the following publication from the Autodesk University written by Darren Young also provides an outline of mapcar, lambda and the apply function:

LISP: Advance Yourself Beyond A Casual Programmer

'Auto CAD > lisp' 카테고리의 다른 글

Error Handling  (0) 2021.02.20
Prompting with a Default Option  (0) 2021.02.20
달수 ▷ ActiveX 처음 시작하기전에 읽어보세요  (2) 2019.04.05
VL-remove (리스트에서 요소제거하기)  (0) 2019.04.04
vla-ZoomScaled (메써드)  (0) 2019.04.04

댓글()