Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Mapcar for Dummies Part 2

36 views
Skip to first unread message

Bobby Jones

unread,
Jul 24, 2001, 4:58:17 PM7/24/01
to
This is a follow-up to my highly successful thread, "Mapcar for Dummies Part
1" on 7/13/01. <VBG> Thanks to Jason Piercey and Rob Starz for jumping in
to save me from myself on that one! This part will continue on with the
(foreach) comparison and will also include a discussion on the (lambda)
function. So let's get to it.

In the previous discussion we went through an example of how to apply the
(1+) function to every item in a list using (mapcar). Let's look at a
couple of other simple examples before moving on. Let's say that we have a
list of dotted pairs and we want to get a list of all the first, (car)'s,
and second, (cdr)'s, elements of each pair.

(setq lst '(("Layer1" . 0) ("Layer2" . 0) ("Layer3" . 1)))

(setq carLst (mapcar 'car lst))
;;;carLst = ("Layer1" "Layer2" "Layer3")

(setq cdrLst (mapcar 'cdr lst))
;;;cdrLst = (0 0 1)

As you can see, (mapcar), returns a list of the results of executing any
single function using each element of the supplied list(s) as arguments.
Notice that I said list(s), inferring that there could be more than one list
supplied as arguments to the (mapcar) function. We will discuss this in
more depth in just a bit. Let's look at something a little more complex.
In the next example we are taking a list of numbers, performing a test on
that number, and then performing a calculation based on the outcome of the
test. First, how to do it with (foreach):

(setq lst '(-10 -5 10 15))

(foreach item lst
(if (< item 5)
(setq newItem (1- item))
(setq newItem (1+ item))
)
(setq newLst (cons newItem newLst))
)
(setq lst (reverse newLst))
;;;lst = (-11 -6 11 16)

So, each item in the list is tested and if it's less than five, 1 is
subtracted from the number and if it's five or higher, then 1 is added to
the number. Now let's do it with (mapcar).

(setq lst '(-10 -5 10 15))

(setq lst (mapcar '???

Wait a minute, there is no built in lisp function that will test a number
and add or subtract to it like we did above. What do we do? Well, let's
define ourselves a new function to use:

;;;Tests a number to determine if it's less than five
;;;if it's less, then it subtracts one from it
;;;if it's 5 or higher, then it adds one to it
;;;it returns the new number
(defun myFunc (item)
(if (< item 5)
(1- item)
(1+ item)
)
)

Now let's use that with (mapcar):

(setq lst (mapcar 'myFunc lst))
;;;lst = (-11 -6 11 16)

Is anybody else thinking what I am thinking? I'm thinking that it sure is
counter-productive to create an entirely new function to feed to (mapcar)
just for this simple little calculation. It's not like we will be adding
that function to our function library. It's also a pain when debugging or
just reviewing old code to have to go and find the (myFunc) function.
Wouldn't it be nice if we could somehow group some code together and feed it
to (mapcar) without defining a new function with (defun)? Guess
what.........we can with the (lambda) function.

From the help files (lambda), "Defines an anonymous function.". OK, so
what's an anonymous function and how do we use it? Quite simply, an
anonymous function is a user defined function, just like (myFunc) above,
except that it has no name. (myFunc) would look like this when defined with
(lambda) instead of (defun myFunc ...):

(lambda (item)
(if (< item 5)
(1- item)
(1+ item)
)
)

(lambda) replaces the (defun) and the name that you would give a function.
Otherwise the syntax and rules are exactly the same as (defun). You can
pass arguments to a (lambda) function like we passed 'item' above. You can
also declare variables as local to a (lambda) function.

(lambda (/ var1 var2)
(setq var1 "This var is local to this lambda function"
var2 "This var is also local to this lambda function")
)

Another note to remember is that a (lambda) function will execute
immediately when loaded, unless it is quoted.

'(lambda (item)
(if (< item 5)
(1- item)
(1+ item)
)
)

You will most often see and use a (lambda) function as a quoted function
being passed to another function like (mapcar). So, to use it with our
(mapcar) function:

(setq lst '(-10 -5 10 15))

(setq lst (mapcar '(lambda (item)
(if (< item 5)
(1- item)
(1+ item)
)
)
lst
)
)
;;;lst = (-11 -6 11 16)

This code, as opposed to the (foreach) loop above, is easier to read, and to
write, and it executes faster. Look this over a while and digest it. If
you are still having trouble deciphering what is happening, write down a
step by step procedure of what this routine is doing with each piece of
data, just like we did in part 1.

Are you starting to understand these two functions? Are you now a master
(mapcar)'er? Good. Let's throw another twist into the equation. As I
hinted in the beginning, (mapcar) will take any number of lists as arguments
to be processed. So far we've only been working with one. Let's look at an
example using more than one list:

(setq lst1 '("Layer1" "Layer2" "Layer3")
lst2 '(0 0 1)
)

(setq lst (mapcar 'cons lst1 lst2))
;;;lst = (("Layer1" . 0) ("Layer2" . 0) ("Layer3" . 1))

Here, (mapcar) took the first element from the first list and used it as the
first argument to the (cons) function. It used the first element in the
second list as the second argument to the (cons) function. Can you guess
what it did with the second and third items in the two lists? Here it is
visually:

(cons "Layer1" 0) = ("Layer1" . 0)
(cons "Layer2" 0) = ("Layer2" . 0)
(cons "Layer3" 1) = ("Layer3" . 1)

It then returns all that in a list format, (("Layer1" . 0) ("Layer2" . 0)
("Layer3" . 1)). (mapcar) will take any number of lists as arguments, but
the number of lists MUST match the number of arguments expected by the
function. In our example above, (cons) expects two arguments, therefore you
must supply two lists of the same length to (mapcar) or you will get an
error.

Hopefully going through some of these exercises has given you a much better
understanding of (mapcar) & (lambda). However, I believe that the best way
to truly learn something is to get your hands dirty with it. If you are
willing, I'll give you a chance to write some useful code using these new
functions. Here is my challenge, calculate the midpoint of a line, using
only:
1) mapcar
2) lambda
3) +
4) *
Use whatever functions you need to use to get the line from the user and
pull out the end points.

Here's an outline of what you need to do, I'll let you decide how much error
checking to include:
1) Ask the user to select a line.
2) get the end points, 10 and 11 association codes.
3) Add the X values together of the two points and multiply by 0.5
4) Add the Y values together of the two points and multiply by 0.5
5) Add the Z values together of the two points and multiply by 0.5
6) Combine the new X, Y, & Z values together into a new point list
7) Return the new point list

I find that the best way to approach a problem like this is to start with
some simple data where I already know the answer. For Instance, I know that
a line drawn from 2,2,0 to 6,6,0 will have a midpoint at 3,3,0. Now how
do I write a function that will take the first two data lists and return the
desired midpoint list? Hmmmm. Enjoy. Post your answers here and/or contact
me directly for a hint.

This concludes Part 2. In the next installment, I'll get into building some
useful utilities and using (mapcar) with other functions such as (apply).
As always, looking forward to your comments.
--
Bobby C. Jones


DonB

unread,
Jul 24, 2001, 6:37:55 PM7/24/01
to
Checked out Mike Pucketts site and found a little jewel under Mapcar
discussion.

Assuming p1 = '(10.0 20.0 30.0) ...
(mapcar 'set '(x y z) p1)
Now x = 10.0, y = 20.0, z = 30.0

I never thought of this. This will save my fingers. :-)

Don


"DonB" <Donald...@email.msn.com> wrote in message
news:AB75A2325FB7F341...@in.WebX.maYIadrTaRb...
> Hopefully someone can show us a more elegant method.
>
> (defun c:prac (/ linList)
> (setq linList (entget (car (entsel "\nSelect line..."))))
> (mapcar (function (lambda (x y) (/ (+ x y) 2)))
> (cdr (assoc 10 linList))
> (cdr (assoc 11 linList))
> )
> )
>
> Don
>
> "Bobby Jones" <bjo...@beazer.com> wrote in message
> news:15A34522E8534F78...@in.WebX.maYIadrTaRb...

Luis

unread,
Jul 24, 2001, 6:37:32 PM7/24/01
to
Example provided by Luis Esquivel July 2001
Here is one of my functions with the use of MAPCAR this will divide a vector
in N parts.

;;; return a point over a vector [a-b]
;;; at divided at desired "n" of parts
;;; from point "a"
;;; usage:
;;; (setq pt (qcad-divide a b 2))
;;; (1273.07 763.193 0.0)
(defun qcad-divide-vector (a b n)
(mapcar '+
a
(mapcar '/
(mapcar '- b a)
(list n n n)
)
)
)


Good luck,
-Luis Esquivel

"Bobby Jones" <bjo...@beazer.com> wrote in message
news:15A34522E8534F78...@in.WebX.maYIadrTaRb...

Eric S.

unread,
Jul 24, 2001, 6:18:13 PM7/24/01
to
Kind of a pickieoony <sp> point:

> For Instance, I know that a line drawn from 2,2,0 to 6,6,0 will have a
midpoint at 3,3,0.

Really, the mid point would be 4,4,0 wouldn't it?

Thanks for the tutor. I've dabbled with this many times and have to tinker,
tinker, tinker to get it working. Your description of it is a better
beginner level (like me) source for help than I've seen in the docs or on
most websites. A heavier duty, not so beginnerish description is at Michael
Pucketts site: http://www.cadvision.com/puckettm/autolisp/mapcar/.

Once again, thanks for taking the time to bring us up on this one.
--

Regards,
Eric S.
eschn...@jensenprecast.com


DonB

unread,
Jul 24, 2001, 6:27:56 PM7/24/01
to
Hopefully someone can show us a more elegant method.

(defun c:prac (/ linList)
(setq linList (entget (car (entsel "\nSelect line..."))))
(mapcar (function (lambda (x y) (/ (+ x y) 2)))
(cdr (assoc 10 linList))
(cdr (assoc 11 linList))
)
)

Don

"Bobby Jones" <bjo...@beazer.com> wrote in message
news:15A34522E8534F78...@in.WebX.maYIadrTaRb...

Doug Broad

unread,
Jul 24, 2001, 10:54:05 PM7/24/01
to
Great tutorial Bobby!,

Some examples in my work for the thread:
;Example 1:
;To reorganize pick points for a rectangle to ensure one will be lower left and the other will
be upper right.
;p1 and p2 are pick points for rectangle
(setq
llc (mapcar 'min (p1 p2)) ;lower left corner
urc(mapcar 'max (p1 p2)) ;upper right corner
)
(mapcar 'set '(w h) (mapcar '- urc llc)) ;width and height of rectangle

;Example 2:
;To make a list of numbers all positive
(mapcar 'abs lst)

;Example 3:
;To make a list of numbers all real
(mapcar 'float lst)

Example 4:
;Does string W match any string in list L regardless of case and where names in list l include
wildcards?
(defun wcmatchl (W L)
(apply 'or (mapcar '(lambda (n) (wcmatch (strcase W)(strcase n))) L))
)

Example 5:
;To find out how many items in a list match a condition
(defun howmanyare (test lst)
(apply '+ (mapcar '(lambda(n) (if (test n) 1 0)) lst))
)
;EX: (howmanyare (lambda (x) (> x 5)) '(0 5 10 15 7)) ;how many are greater than 5?
; returns 3
;EX: (howmanyare minusp '(0 -1 3 4 -4)); how many are negative?
;returns 2

BTW, function can be used with lambda rather than quote to optimize the expression when used
with (vlisp-compile...
Your example would translate as
(setq lst (mapcar (function (lambda (item)


(if (< item 5)
(1- item)
(1+ item)
)

))


lst
)
)
;;;lst = (-11 -6 11 16)

Doug Broad
A2k2

"Bobby Jones" <bjo...@beazer.com> wrote in message
news:15A34522E8534F78...@in.WebX.maYIadrTaRb...

...

Bill Wilkerson

unread,
Jul 25, 2001, 10:05:35 AM7/25/01
to
"Doug Broad" <dbr...@earthlink.net> wrote
> Example 4:

> ;Does string W match any string in list L regardless of case and where
names in list l include
> wildcards?
> (defun wcmatchl (W L)
> (apply 'or (mapcar '(lambda (n) (wcmatch (strcase W)(strcase n))) L))
> )
>

Code police here...

Many people make this mistake because they do not understand that the OR
function does 'conditional evaluation' (IOW, the OR function stops
evaluating arguments once it determines the result).

But because of the way you wrote both of the above examples, they require
all of the items in the list to be tested, and they continue testing items
even after the result is determined. Not good!

The vl-some and vl-every functions were designed precisely for this
purpose - they do conditional evaluation with lists and do not continue to
process list elements after a result is determined.

The right way to implement the function in your example 4 would be:

(defun wcmatch-list (w l)
(setq w (strcase w))
(vl-some '(lambda (s) (wcmatch w (strcase s))) l)
)

One way to clearly see the difference between using (vl-some) and (apply 'or
(mapcar blah blah blha)) is to first do this on the command line:

Command: (trace wcmatch)
WCMATCH

Then, assign the arguments to symbols:

Command: (setq str "ABCDEFG")
"ABCDEFG"

Command: (setq strlist '("AZ*" "AX*" "AB*" "AD*" "AF*" "X" "Y" "Z"))
("AZ*" "AX*" "AB*" "AD*" "AF*" "X" "Y" "Z")

Now, let's try your function:

Command: (wcmatchl str strlist)
Entering (WCMATCH "ABCDEFG" "AZ*")
Result: nil
Entering (WCMATCH "ABCDEFG" "AX*")
Result: nil
Entering (WCMATCH "ABCDEFG" "AB*")
Result: T
Entering (WCMATCH "ABCDEFG" "AD*")
Result: nil
Entering (WCMATCH "ABCDEFG" "AF*")
Result: nil
Entering (WCMATCH "ABCDEFG" "X")
Result: nil
Entering (WCMATCH "ABCDEFG" "Y")
Result: nil
Entering (WCMATCH "ABCDEFG" "Z")
Result: nil
T
Command:

Note how many times WCMATCH (and strcase) was called, and note that the
third time WCMATCH was called, the result of your function is known.. But,
your function is acting like the Eveready bunny rabbit!

Now, let's try my function:

Command: (wcmatch-list str strlist)
Entering (WCMATCH "ABCDEFG" "AZ*")
Result: nil
Entering (WCMATCH "ABCDEFG" "AX*")
Result: nil
Entering (WCMATCH "ABCDEFG" "AB*")
Result: T
T

You see, (VL-SOME) stops processing its list argument as soon as the
predicate function you pass it returns anything but NIL, so you can do the
same kind of conditional evaluation on lists thats done by the AND and OR
functions on their arguments and avoid needless execution.

Another rule your function breaks is that it needlessly evaluates a constant
expression numerous times (a constant expression is one whose result is
always the same). In this case, within the lambda function you call
(strcase), and pass it the string assigned to W, even though that string
never changes. So, why do you need to convert it to upper case more than
once? You don't. Note that in my version of your function, this is corrected
too. It calls STRCASE only once with the first argument, and assigns the
result to the same symbol.

Most of this also applies to your example 5.

Regards,
Bill

Eric S.

unread,
Jul 25, 2001, 12:01:01 PM7/25/01
to
(defun half (pt1 pt2 fac)
(mapcar '(lambda (a b)(+ a (* (- b a) fac))) pt1 pt2)
)

Eric S.

unread,
Jul 25, 2001, 12:07:35 PM7/25/01
to
I guess the function could have been given a better name and a bit of a
description.

;;;Returns a point at a factor of the distance between the two points you
provide
;;;usage (factorpoint (getpoint)(getpoint) 0.5) or (factorpoint
(getpoint)(getpoint) 2)
(defun factorpoint (pt1 pt2 fac)

Bobby Jones

unread,
Jul 25, 2001, 12:58:12 PM7/25/01
to
Professional or not<g>, you have pointed out my blatant error! I'm batting
1000 so far, any bets as to how long I can keep up my streak? The smart
gambler would put everything on my continued boneheadedness :-) And thanks
for the link to Micheal's page. It's a superb brain dump on this subject
and you come out of it with an excellent routine to add to your library.
--
Bonehead Bob


Bobby Jones

unread,
Jul 25, 2001, 1:04:23 PM7/25/01
to
Now Don, don't go and confuse my target audience with that (function)
function :-) I hadn't actually planned on mentioning it in this series of
ramblings. <hint>In fact it was my hope that they would inspire others to
add to and/or continue on where I left off.</hint> Thanks for the
contribution. Power to the people!!
--
Bobby C. Jones

Bobby Jones

unread,
Jul 25, 2001, 4:46:41 PM7/25/01
to
Instead of saying thanks to each of you individually, I'll just say thanks
to all participants now. With special thanks to Bill for policing, because
Lord knows we, especially me, need it!
--
Bobby C. Jones

Cliff Middleton

unread,
Jul 25, 2001, 5:32:15 PM7/25/01
to
Keep 'em coming, Bobby C. I enjoy (and learn/relearn from) the threads.
-
Cliff

"Bobby Jones" <bjo...@beazer.com> wrote in message

news:82622CE652736D3B...@in.WebX.maYIadrTaRb...

Eric S.

unread,
Jul 25, 2001, 6:12:40 PM7/25/01
to
Easy now! Don't beat yourself up so bad. We still love ya. After all, nobody
else loves us enough to present an unsolicited brain booster.

Doug Broad

unread,
Jul 25, 2001, 7:28:35 PM7/25/01
to
Thanks Bill,
Good contribution. When I wrote the function in 1999, I wasn't using VLISP.
Thanks for illustrating the vl-some function. I will read up on them. I knew there
would be some extra processing but the lists I would be passing were to be relatively
short and the extra few milliseconds wasn't an issue. I like your rewrite and discussion.

Doug Broad

"Bill Wilkerson" <bi...@no-spam.my-deja.com> wrote in message
news:3A50F28DD3080875...@in.WebX.maYIadrTaRb...


> Many people make this mistake because they do not understand that the OR
> function does 'conditional evaluation' (IOW, the OR function stops
> evaluating arguments once it determines the result).
>
> But because of the way you wrote both of the above examples, they require
> all of the items in the list to be tested, and they continue testing items
> even after the result is determined. Not good!

> The right way to implement the function in your example 4 would be:

pi

unread,
Jul 26, 2001, 12:25:59 PM7/26/01
to
I know I'm probably jumping in a couple of days late but I've just bought a
new computer set-up and only managed to get to the newsgroups
today.................Anyhow, I'm sure glad I did in time to get your
"part2" and I cant wait to get the time to read and learn learn learn.
Thanks Bobby for the time, effort, and etc.
Pete

0 new messages