[net.lang.lisp] programming trick

manuel@cui.UUCP (03/01/86)

I've often run into the problem of wanting to apply a function to
every element of a list, but been stuck because the function takes
more than one argument.  As a simple example:

  (defun F (x y)
    (+ x y))

  (setq L '(2 4 6 8))

How do we add 10 to each element of L?

  (mapcar 'F L '10)                  ; Error: bad list argument to map

  (mapcar 'F L '(10))                ; (12)

  (mapcar 'F L '(10 10 10 10))       ; (12 14 16 18)

  (mapcar '(lambda (x) (F x 10)) L)  ; (12 14 16 18)

The first example doesn't work for obvious reasons.  The second returns
a list whose length is the minimum of the argument lists which were given
to mapcar.  The third works correctly, but is cumbersome because we first
had to know that the length of L was 4.  The fourth is the best solution,
but obscures the code because the number 10 has been hidden inside the
lambda (this gets much worse for more complicated expressions).  It would
be better to have something with the form of the first or second examples.
The following trick can be used to do this:

  (defun repeated (x)              ; returns an 'infinite' list of x's
    ((lambda (list-x)
       (rplacd list-x list-x))
     (list x)))

  (mapcar 'F L (repeated '10))     ; (12 14 16 18)

The 'repeated' function returns a CONS cell whose CDR points to the cell
itself.  This results in an (apparant) infinite list of x's.  Using this
as an argument list to mapcar is clearer and doesn't depend on the lengths 
of the other argument lists.  In general, it also costs less in space and
in time.

Hope this helps ...


James Stewart
University of Geneva

UUCP:   seismo!mcvax!cernvax!cui!manuel
BITNET: stewart@cgeuge52

hucka@utah-cs.UUCP (Michael Hucka) (03/01/86)

In article <136@cui.UUCP> manuel@cui.UUCP (James Stewart) writes:
> I've often run into the problem of wanting to apply a function to
> every element of a list, but been stuck because the function takes
> more than one argument.  As a simple example:
>
>  (defun F (x y) 
>    (+ x y))
>
>  (setq L '(2 4 6 8))
>
> How do we add 10 to each element of L?
>
> [examples and more discussion . . .]

As an alternative, you could also do the following (assumine "L" and "F"
are already defined):

   (let ((arg2 10))
     (mapcar #'(lambda (arg1) (F arg1 arg2)) L))

Whether this works correctly may depend on the scoping aspects of the dialect
of Lisp you are using.  I believe this code is fairly transparent, though
others may disagree with me . . . .

							Mike
-- 
...........................................................................
::::::  Mike Hucka {ihnp4 decvax}!utah-cs!hucka, hucka@utah-cs.ARPA    ::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

avolio@decuac.UUCP (03/08/86)

In <136@cui.UUCP>, manuel@cui.UUCP (MANUEL Alfred) writes: 
> I've often run into the problem of wanting to apply a function to
> every element of a list, but been stuck because the function takes
> more than one argument.  As a simple example:
>  
>   (defun F (x y)
>     (+ x y))
> 
>   (setq L '(2 4 6 8))
> 
> How do we add 10 to each element of L?
> ...
> The following trick can be used to do this:
> 
>   (defun repeated (x)              ; returns an 'infinite' list of x's
>     ((lambda (list-x)
>        (rplacd list-x list-x))
>      (list x)))
> 
>   (mapcar 'F L (repeated '10))     ; (12 14 16 18)


I read this with a deep sense of deja vu, having done this 8 years ago
or so.  Please,  understand this is not a put-down.  By no means! I
think that Manuel Alfred is quite clever, and I like to see thought
put in to making code more elegant and into creating useful tools.  I
did want to pass on some things along the lines of what he is doing.

A number of years ago some ideas along these lines were started by
some ideas put down on paper in some Indiana University Computer
Science Dept. Technical reports.  Now, all of the reports I own have
yet to be found (still boxed after a number of moves, I suspect).  But
what I am going to pass on here started with "CONS Should Not Evaluate
Its Arguments" by Friedman and Wise and were implemented after a paper
by the same professors regarding Functional Combination.

In a distributed computing environment (I am only going to touch on this
here because what I remember is from reading 8 years ago and I will get
myself in deep trouble with the above mentioned authors, especially the
Friedman part!) you want to be able to evaluate things in parallel.  So
you have a matrix  like this:

		[ Fun1  Fun2  Fun3 ... FunN ]
		[ x1     x2    x3  ...  xN  ]   
		[ y1     y2    y3  ...  yN  ]
			...
		[ z1     z2    z3  ...  zN  ]

where Fun1 is applied to x1 through z1, Fun2 to x2 through z2, etc.  The
result, usually, is a list of 'answers.'  (Although in the paper, they
intended that the result is the value of whichever finishes processing
first -- again in a parallel computing environment.)

So, we could have a function fc, which does these applications and be
able to say:

	FC: [ sum  length ]	
	    [ (1 2 3 4) (a b c d e f) ]

and expect to get the answer (10 6), where 10 is the result of applying
sum to (1 2 3 4) -- add the list elements -- and 6 is from applying
length to (a b c d e f).  Or, if you wanted to apply sum and length to
the same argument,

	FC: [ sum length ]
	    [ (1 2 3 4)* ]

resulting in (10 4).  The '*' function is Mr. Alfred's 'repeated'
function, or  'star' (below).  So we have two functions, we can use for
the kinds of things Mr. Alfred suggests in many different ways.

So, we have fc and star (I will include fchelp later).

	(def fc
	  (lambda (funlist argmatrix)
	    (fchelp funlist 'cons argmatrix)))
	
	(def star
	  (lambda (xxx)
	    ((lambda (lxxx)
	             (rplacd lxxx lxxx))
	      (list xxx))))


So, (fc (list 'plus 'length) (star L)) (where L is (1 2 3 4)) returns
(10 4). (By the way, fc was originally written as a FEXPR so that it
could be called in a 'prettier' manner.  I rewrote it since I have not
used Lisp in quite a while and could not find FEXPRs.  Maybe they are
old-fashioned and I am dating myself... Note, below the function
delete seems to be different than years back, and I had to write my
own.)  I have used fc extensively in many different programs. (Once
you find a useful function you tend to use it as a tool, yes?)  A few
more interesting but unelaborate examples:

Combinations of a set:

(def combine
  (lambda (l)
    (cond ((null l) (list nil))
          (t (append (combine* (car l) (cdr l)) (combine (cdr l)))))))

(def combine*
  (lambda (a l)
    (fc (star 'cons) (list (star a) (combine l)))))

So, (combine '(a b c)) returns
	((a b c) (a b) (a c) (a) (b c) (b) (c) nil)

Permutations of a set:

(def permute
  (lambda (l)
    (cond ((null l) nil)
          ((null (cdr l)) (list l))
          (t
           (fca (star 'mapcons)
                (list l
                      (fc (star 'permute)
                          (list
                           (fc (star 'delete) (list l (star l)))))))))))

produces: ((a b c) (a c b) (b a c) (b c a) (c a b) (c b a))

Oh... fca used above is just like fc but uses append instead of cons to
put things together.  Please note that the repeated or star function can
be used with the function as well as the arguments.  Also, note how fc is
used differently.  For example, it is used to take a list and produce
copies of it -- each with one of the elements missing.  

	(setq L '(a b c d e f g))
	(fc (star 'delete) (list L (star L)))

results in: ((b c d e f g) (a c d e f g) (a b d e f g) (a b c e f g) 
             (a b c d f g) (a b c d e g) (a b c d e f))

Fun, huh?  Additionally, if we are using functions that have differing
numbers of arguments, we can still use the fc function.  Example:

	(fc '(add1 plus) (list '(@ 3) '(5 9)))

gives us (6 12).  Note, '@' is used as a 'dead' element -- one to skip
over.  (This is why 'psucars' -- for pseudo-cars -- is used.)


I am including all of the functions mentioned above at the end of
this.  At one time the idea of putting neat, fun, elegant, small, Lisp
tools into a book was kicked around.  The idea was to collect such
tools (or Lispoems, because of the way they affect the 'reader' and
their 'elegance' and 'flow' -- I wonder .. there may be just one other
individual in the world who reads this and doesn't think I'm crazy!)
into a small book.  But, over the years it has been put terminally
aside.  (If anyone reading this thinks this sounds like a good idea,
drop me a note.)

Functions follow.

	(def fc
	  (lambda (funlist argmatrix)
	    (fchelp funlist 'cons argmatrix)))
	
	(def fca
	  (lambda (funlist argmatrix)
	    (fchelp funlist 'append argmatrix)))
	
	(def star
	  (lambda (xxx)
	    ((lambda (lxxx)
	             (rplacd lxxx lxxx))
	      (list xxx))))
	
	(def combine
	  (lambda (l)
	    (cond ((null l) (list nil))
	          (t (append (combine* (car l) (cdr l)) (combine (cdr l)))))))
	
	(def combine*
	  (lambda (a l)
	    (fc (star 'cons) (list (star a) (combine l)))))
	
	(def permute
	  (lambda (l)
	    (cond ((null l) nil)
	          ((null (cdr l)) (list l))
	          (t
	           (fca (star 'mapcons)
	                (list l
	                      (fc (star 'permute)
	                          (list
	                           (fc (star 'delete) (list l (star l)))))))))))
	
	(def fchelp
	  (lambda (funlist consfun argmat)
	    (cond ((null funlist) nil)
	          ((member nil argmat) nil)
	          (t
	           (apply consfun
	                  (list (apply (car funlist) (psucars argmat))
	                        (fchelp (cdr funlist)
	                                consfun
	                                (mapcar (function cdr) argmat))))))))
	
	(def psucars
	  (lambda (l)
	    (cond ((null l) nil)
	          ((eq (caar l) '@) (psucars (cdr l)))
	          (t (cons (caar l) (psucars (cdr l)))))))
	
	(def mapcons
	  (lambda (x l)
		(fc (star 'cons) (list (star x) l))))
	
	(def delete
	  (lambda (a l)
	    (cond ((null l) nil)
	          ((eq a (car l)) (delete a (cdr l)))
	          (t (cons (car l) (delete a (cdr l)))))))


--
Fred @ DEC Ultrix Applications Center
UUCP: {decvax,seismo,cbosgd}!decuac!avolio       INET: avolio@decuac.DEC.COM