Author Topic: mapcar apply lambda  (Read 11354 times)

0 Members and 1 Guest are viewing this topic.

mailmaverick

  • Bull Frog
  • Posts: 494
mapcar apply lambda
« on: January 03, 2014, 07:19:27 AM »
Dear All

I have been using LISP for the past few months and have made some LISP programs successfully.

I have never understood the concept of mapcar, lambda and apply clearly. However, still I have been able to make programs using foreach, repeat, while etc.

My questions are :

(i) Is there any benefit of using mapcar, apply when we can use foreach, repeat, while etc ?

(ii) Instead of lambda we can define a function with name and use it wherever required.  So what is the benefit of lambda ? I have read that it saves memory. But question is how much memory does our LISP programs consume and what we will get by saving it and that too only during running of routine.





« Last Edit: January 03, 2014, 07:38:38 AM by mailmaverick »

Peter2

  • Swamp Rat
  • Posts: 650
Re: mapcar apply lambda
« Reply #1 on: January 03, 2014, 02:09:44 PM »
Peter

AutoCAD Map 3D 2023 German (so some technical terms will be badly retranslated to English)
BricsCAD V23

mailmaverick

  • Bull Frog
  • Posts: 494
Re: mapcar apply lambda
« Reply #2 on: January 05, 2014, 02:47:14 AM »
I have read the tutorials but my query is still unresolved.

Is there any situation where the logic cannot be achieved by foreach, repeat. while etc but can be achieved by mapcar, apply etc ?

If yes, then I would like to know such a case.

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: mapcar apply lambda
« Reply #3 on: January 05, 2014, 05:38:43 AM »
Is there any situation where the logic cannot be achieved by foreach, repeat. while etc but can be achieved by mapcar, apply etc.

There is usually more than one way to tackle a problem in AutoLISP, and indeed, every conceivable loop could probably be constructed using only foreach, repeat or while. However, although possible, for some situations this will be far more inefficient and obfuscated than writing an equivalent statement using mapcar (apply is not comparable as this function does not iterate over a list).

The first example that springs to mind:

"Write a function that sums the items in two lists and outputs a list of the results."

For example, given two lists: (1 2 4 6 3 8 7) & (5 4 7 2 3 1 9), the result should be (6 6 11 8 6 9 16).

Using foreach, this might be written as:
Code - Auto/Visual Lisp: [Select]
  1. (defun sum-foreach ( a b / r y )
  2.     (foreach x a
  3.         (if (setq y (car b))
  4.             (setq r (cons (+ x y) r)
  5.                   b (cdr b)
  6.             )
  7.         )
  8.     )
  9.     (reverse r)
  10. )
Code - Auto/Visual Lisp: [Select]
  1. _$ (sum-foreach '(1 2 4 6 3 8 7) '(5 4 7 2 3 1 9))
  2. (6 6 11 8 6 9 16)

Using repeat, it might be:
Code - Auto/Visual Lisp: [Select]
  1. (defun sum-repeat ( a b / x y r )
  2.     (repeat (min (length a) (length b))
  3.         (if
  4.             (and
  5.                 (setq x (car a))
  6.                 (setq y (car b))
  7.             )
  8.             (setq r (cons (+ x y) r)
  9.                   a (cdr a)
  10.                   b (cdr b)
  11.             )
  12.         )
  13.     )
  14.     (reverse r)
  15. )
Code - Auto/Visual Lisp: [Select]
  1. _$ (sum-repeat '(1 2 4 6 3 8 7) '(5 4 7 2 3 1 9))
  2. (6 6 11 8 6 9 16)

Using while, it could be written as:
Code - Auto/Visual Lisp: [Select]
  1. (defun sum-while ( a b / x y r )
  2.     (while
  3.         (and
  4.             (setq x (car a))
  5.             (setq y (car b))
  6.         )
  7.         (setq r (cons (+ x y) r)
  8.               a (cdr a)
  9.               b (cdr b)
  10.         )
  11.     )
  12.     (reverse r)
  13. )
Code - Auto/Visual Lisp: [Select]
  1. _$ (sum-while '(1 2 4 6 3 8 7) '(5 4 7 2 3 1 9))
  2. (6 6 11 8 6 9 16)

However using mapcar, this task is a simple one-liner:
Code - Auto/Visual Lisp: [Select]
  1. (defun sum-mapcar ( a b ) (mapcar '+ a b))
Code - Auto/Visual Lisp: [Select]
  1. _$ (sum-mapcar '(1 2 4 6 3 8 7) '(5 4 7 2 3 1 9))
  2. (6 6 11 8 6 9 16)

And the use of the right tool for the job is reflected in the performance:
Code - Auto/Visual Lisp: [Select]
  1. _$ (setq a '(1 2 4 6 3 8 7) b '(5 4 7 2 3 1 9))
  2. (5 4 7 2 3 1 9)
  3. _$ (repeat 5 (setq a (append a a) b (append b b)))
  4. _$ (length a)
  5. 224
  6. _$ (benchmark '((sum-foreach a b)(sum-repeat a b)(sum-while a b)(sum-mapcar a b)))
  7. Benchmarking .................Elapsed milliseconds / relative speed for 16384 iteration(s):
  8.  
  9.     (SUM-MAPCAR A B)......1451 / 6.56 <fastest>
  10.     (SUM-WHILE A B).......7363 / 1.29
  11.     (SUM-FOREACH A B).....7551 / 1.26
  12.     (SUM-REPEAT A B)......9516 / 1.00 <slowest>

And now consider if we want to sum the items in not just two lists, but n lists...

Using mapcar, this is again a simple one-liner:
Code - Auto/Visual Lisp: [Select]
  1. (defun sum-mapcar ( l ) (apply 'mapcar (cons '+ l)))
Code - Auto/Visual Lisp: [Select]
  1. _$ (sum-mapcar '((1 3 4 2) (5 6 2 1) (8 5 1 5) (1 4 5 5)))
  2. (15 18 12 13)

But it certainly wouldn't be anywhere near as concise (or efficient) using only foreach, while or repeat.

Restricting yourself to using only foreach, while or repeat for every situation is like trying to build a house with only a hammer - you are depriving yourself of some powerful tools in your AutoLISP toolbox.

ymg

  • Guest
Re: mapcar apply lambda
« Reply #4 on: January 05, 2014, 12:55:35 PM »
mailmaverick,

Quote
So what is the benefit of lambda ?

Say that the function is only used by that mapcar, there is no point
to clutter your program with a separate funtion call.

On top of that, the function is right there, where it is used, which is
very logical. No need to go anywhere else to assert  what the
function is doing.

ymg

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: mapcar apply lambda
« Reply #5 on: January 05, 2014, 01:48:55 PM »
Hi,

Higher order functions (which, with AutoLISP, means functions which takes another function as arguments), and lambda functions  are basic concepts of functional programming (LISP is the oldest functional programming language).

In my opinion, the understanding of these concepts (as the understanding of recursion) is a major step in the AutoLISP (or any other language) learning curve.

The functional paradigm allows a more declarative way of coding: you directly write what you want instead of describing the way to get what you want.
A simple example to get the even integers from an integers list

Code - Auto/Visual Lisp: [Select]
  1. (setq lst '(0 1 2 3 4 5 6 7 8 9))
  2.  
  3. (defun isEven (x) (zerop (rem x 2)))

Functional/declarative way using vl-remove-if-not (which is a higher order function):
get the list without the odd integers.
Code - Auto/Visual Lisp: [Select]
  1. (setq evens (vl-remove-if-not 'isEven lst))

Imperative way using foreach:
for each item in the list, if the item is even, add it to the new list. Reverse the new list.
Code - Auto/Visual Lisp: [Select]
  1. (foreach x lst (if (isEven x) (setq evens (cons x evens))))
  2. (reverse evens)

Both would have been written without using the external isEven function

Code - Auto/Visual Lisp: [Select]
  1. (setq evens (vl-remove-if-not '(lambda (x) (zerop (rem x 2))) lst))

Code - Auto/Visual Lisp: [Select]
  1. (foreach x lst (if (zerop (rem x 2)) (setq evens (cons x evens))))
  2. (reverse evens)

« Last Edit: January 05, 2014, 01:55:58 PM by gile »
Speaking English as a French Frog

Bhull1985

  • Guest
Re: mapcar apply lambda
« Reply #6 on: January 06, 2014, 11:41:32 AM »
Thanks Gile, this is a good description of how and when to use a lambda function!
I believe the hardest thing to grasp for novices such as myself in regards to lambda and mapcar is intuitively knowing when one is correct method of obtaining a result.
I'm starting to feel like I could use these functions though, and off-the-top of my head I would phrase it as such.

When a separate defun would be required, could be used, to process a LIST of items.
      - A LIST of items because mapcar applies a function to each item within a list, so say we're dealing with a point or two , complete with x y and z coordinates.
Code: [Select]
(setq pt1 '(1.0 1.0 0.0))
(setq pt2 '(2.0 2.0 0.0))

And we want to for whatever reason, add some elevation to these points in order to add a third dimension, or make the points 3-dimensional. We will arbitrarily add an elevation of 1 foot to each of these points.

One method would be to write a defun accepting as arguments the point variable and to which the 1 foot would be added to the z-coordinate.
Code: [Select]
(defun addelev ( pt / x y z newz newpt)
(setq x (car pt))
(setq y (cadr pt))
(setq z (caddr pt))
(if pt
(setq newz (+ 12.0 z))
(princ "\nNot a point coordinate")
);if
(setq newpt (list x y newz))
(princ newpt)
)

well, client work calls. I feel like I'm getting close. My function there prints to the screen (X Y NEWPT) instead of the values themselves and I was going to anonymize that function with a lambda but alas, gotta busy myself elsewhere.
Am I doing something wrong in that function or heading down the right path? Gile, Lee, Others?

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: mapcar apply lambda
« Reply #7 on: January 06, 2014, 01:38:26 PM »
Hi,

You can simply write your function:
Code - Auto/Visual Lisp: [Select]
  1. (defun addelev (pt)
  2.   (list (car pt) (cadr pt) (+ (caddr pt) 12.))
  3. )

Or as a lambda function:
Code - Auto/Visual Lisp: [Select]
  1. (lambda (pt) (list (car pt) (cadr pt) (+ (caddr pt) 12.)))
Speaking English as a French Frog

Bhull1985

  • Guest
Re: mapcar apply lambda
« Reply #8 on: January 06, 2014, 03:36:57 PM »
Thank you!
So

Code: [Select]
(setq pt1 '(1.0 2.0 0.0))
(setq pt2 '(2.0 3.5 0.0))
(setq pt3 '(5.0 10.0 0.0))
(setq pt4 '(6.0 7.5 0.0))
(setq lst (list pt1 pt2 pt3 pt4))
(mapcar
   '(lambda ( pt )
     (list (car pt) (cadr pt) (+ (caddr pt) 12.))
   )
 lst
)

This , if *crosses fingers* would apply the "addelev" to each point in the list , giving each a z coordinate of 12.0, keeping the x and y coordinates the same.
This would be a baby example and I'm not too quick to say it's correct but if I'm taking the right steps here hopefully someone in addition to myself gains from it


edit: cad says it works, hooray.

fixo

  • Guest
Re: mapcar apply lambda
« Reply #9 on: January 06, 2014, 05:01:15 PM »
Maybe i'm missing something,
but I would be use instead:
Code: [Select]
(mapcar '(lambda (pt)(mapcar '+ pt (list 0 0 12))) lst)

Bhull1985

  • Guest
Re: mapcar apply lambda
« Reply #10 on: January 06, 2014, 05:16:06 PM »
Not sure I understand the difference.
I can't follow the logic in yours..nevertheless; it appears to work as well.
I believe it to be adding the z coordinate in a different fashion than the previous example, but you've got an additional argument and an additional mapcar statement.
Twice the complexity, IMO, but perhaps there's a good reason for it.......
Code: [Select]
(setq pt1 '(1.0 2.0 0.0))
(1.0 2.0 0.0)

Command: (setq pt2 '(2.0 3.5 0.0))
(2.0 3.5 0.0)

Command: (setq pt3 '(5.0 10.0 0.0))
(5.0 10.0 0.0)

Command: (setq pt4 '(6.0 7.5 0.0))
(6.0 7.5 0.0)

Command: (setq lst (list pt1 pt2 pt3 pt4))
((1.0 2.0 0.0) (2.0 3.5 0.0) (5.0 10.0 0.0) (6.0 7.5 0.0))

Command: (mapcar '(lambda (pt)(mapcar '+ pt (list 0 0 12))) lst)
((1.0 2.0 12.0) (2.0 3.5 12.0) (5.0 10.0 12.0) (6.0 7.5 12.0))

Command: (setq pt1 '(1.0 2.0 0.0))
(1.0 2.0 0.0)

Command: (setq pt2 '(2.0 3.5 0.0))
(2.0 3.5 0.0)

Command: (setq pt3 '(5.0 10.0 0.0))
(5.0 10.0 0.0)

Command: (setq pt4 '(6.0 7.5 0.0))
(6.0 7.5 0.0)

Command: (setq lst (list pt1 pt2 pt3 pt4))
((1.0 2.0 0.0) (2.0 3.5 0.0) (5.0 10.0 0.0) (6.0 7.5 0.0))

Command: (mapcar
(_>    '(lambda ( pt )
('(_>      (list (car pt) (cadr pt) (+ (caddr pt) 12.))
('(_>    )
(_>  lst
(_> )
((1.0 2.0 12.0) (2.0 3.5 12.0) (5.0 10.0 12.0) (6.0 7.5 12.0))

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: mapcar apply lambda
« Reply #11 on: January 06, 2014, 06:19:13 PM »
Not sure I understand the difference.
I can't follow the logic in yours..nevertheless; it appears to work as well.

See this thread to understand fixo's example :wink:

Bhull1985

  • Guest
Re: mapcar apply lambda
« Reply #12 on: January 07, 2014, 07:14:16 AM »
Excuse me if I'm wrong- just trying to gain understanding here...
and I'm not, definitely not, thinking my methodology is the best here but at the moment the second example, "fixos example" seems a bit redundant with the multiple calls to mapcar and the single list. I suppose with the points being a list themselves, is this what's causing the second mapcar?


This is how I read the two functions occurring;

Fixos adds (0 0 12) to each point, mapping the addition
The previous routine adds 12 to the z coordinate only.

And the code again, side by side for each
Code: [Select]
(mapcar '(lambda (pt)(mapcar '+ pt (list 0 0 12))) lst)

and

(mapcar
   '(lambda ( pt )
     (list (car pt) (cadr pt) (+ (caddr pt) 12.))
   )
 lst
)


Tomato, tomatoe? Or is there a functional difference that i'm unawares of?
According to the linked thread it may be to have all values returned , however..
Code: [Select]
(mapcar
(_>    '(lambda ( pt )
('(_>      (list (car pt) (cadr pt) (+ (caddr pt) 12.))
('(_>    )
(_>  lst
(_> )
((1.0 2.0 12.0) (2.0 3.5 12.0) (5.0 10.0 12.0) (6.0 7.5 12.0))
I'll be looking into this more, surely there's a reason for knowing this other way

mailmaverick

  • Bull Frog
  • Posts: 494
Re: mapcar apply lambda
« Reply #13 on: January 07, 2014, 08:52:22 AM »
Dear Bhull,

I have run both of your functions on a list of 10,000 points.
Your first function with two mapcar takes more time than second function with one mapcar.
The veterans can explain why it is so.

Code: [Select]
(defun c:dff ()
  (setq lst nil)
  (setq a (list 100.4 340.24 452.24))
  (repeat 10000 (setq lst (append lst (list a))))
  (setq Start (getvar "Millisecs"))
  (setq b (mapcar '(lambda (pt) (mapcar '+ pt (list 0.0 0.0 12.0))) lst))
  (setq End (getvar "Millisecs"))
  (setq yu (- End Start))
  (princ "\nTime Taken to Run Method 1 (two mapcar) : ")
  (princ yu)
  (princ " milliseconds \n")
  (setq Start (getvar "Millisecs"))
  (setq c
(mapcar '(lambda (pt) (list (car pt) (cadr pt) (+ (caddr pt) 12.0)))
lst
)
  )
  (setq End (getvar "Millisecs"))
  (setq yu (- End Start))
  (princ "\nTime Taken to Run Method 2 (single mapcar) : ")
  (princ yu)
  (princ " milliseconds \n")
  (princ)
)


Output :-
Time Taken to Run Method 1 (two mapcar) : 140 milliseconds

Time Taken to Run Method 2 (single mapcar) : 47 milliseconds

jvillarreal

  • Bull Frog
  • Posts: 332
Re: mapcar apply lambda
« Reply #14 on: January 07, 2014, 11:56:02 AM »
Hi mailmaverick / Bhull1985,

As you know, there are many ways to accomplish the same task.
The example provided to you by Fixo is one of brevity, also providing you with a method to quickly add to each item of your list.

Here's another for shtzngigglez:

Code: [Select]
(setq a (list 100.4 340.24 452.24))
  (repeat 20000 (setq lst (append lst (list a))))

(defun test1 ()
(mapcar'(lambda (pt)(reverse (cons (+ (last pt) 12)(cdr (reverse pt)))))lst)
)

(defun test2 ()
(mapcar '(lambda (pt)(mapcar '+ pt (list 0 0 12))) lst)
)

(defun MavBull ()
(mapcar '(lambda (pt) (list (car pt) (cadr pt) (+ (caddr pt) 12.0)))lst)
)

I found the difference in time to be negligible and inconsistent (on my machine at least):

$ (benchmark '(mavbull test1 test2))
Benchmarking .......................Elapsed milliseconds / relative speed for 1048576 iteration(s):

    TEST2.......1108 / 1.03 <fastest>
    TEST1.......1123 / 1.01
    MAVBULL.....1139 / 1.00 <slowest>
_$ (benchmark '(mavbull test1 test2))
Benchmarking .......................Elapsed milliseconds / relative speed for 1048576 iteration(s):

    TEST2.......1139 / 1.01 <fastest>
    MAVBULL.....1154 / 1.00
    TEST1.......1154 / 1.00 <slowest>
_$ (benchmark '(mavbull test1 test2))
Benchmarking .......................Elapsed milliseconds / relative speed for 1048576 iteration(s):

    TEST1.......1139 / 1.01 <fastest>
    MAVBULL.....1154 / 1.00
    TEST2.......1154 / 1.00 <slowest>
« Last Edit: January 07, 2014, 12:10:25 PM by jvillarreal »