Page 1 of 2

averaging

Posted: Tue Aug 31, 2010 4:32 pm
by burton
hi,
trying to solve this problem:
a function that adds up the numbers at the end of lists and then averages them.
input is like (xxx 1) (yyy 2) (zzz 3)....(nnn n)
=>6 ;(1+2+3+..+n)/n

(defun averaging (lst)
(if (null lst)
0
(+(setq(last lst)?

how does it register the number of lists it has visited, which will be the denominator of the average?
there is some sort of recursion i guess..

thnx

Re: averaging

Posted: Tue Aug 31, 2010 5:44 pm
by edgar-rft
If the input is a list of lists like:

Code: Select all

(list ('xxx 1) ('yyy 2) ('zzz 3) ... ('nnn n))
and it's always the same math operation [e.g. addition] and used always with the same element in the lists [e.g. the last element] then I would try to use REDUCE [look at the examples at the bottom of the page] instead of recursion to compute the sum of the numbers.

Hint: with the input list from above, the denominator is equal to the LENGTH of the list.

Re: averaging

Posted: Tue Aug 31, 2010 6:48 pm
by s-imp
I'm sure this is bad but:

Code: Select all

(defun average-list (lst)
  (/
     (eval (append '(+)
                  (mapcar #'(lambda (x) (first (last x))) lst)))
     (length lst)))


Re: averaging

Posted: Tue Aug 31, 2010 7:26 pm
by Kohath
s-imp wrote:I'm sure this is bad ...
Good idea, try apply rather than eval with append:

Code: Select all

(defun average-list (lst)
  (/ (apply #'+
            (mapcar #'(lambda (x) (first (last x))) lst))
     (length lst)))
More could be done, but it works nicely. The next step is to use reduce, which would be more concise.

Hint: You'd want to use the same function you passed to mapcar as the :key argument to reduce.

Re: averaging

Posted: Tue Aug 31, 2010 8:05 pm
by Duke
Here's a closure, just for fun:

Code: Select all

(defun make-averager ()
           (let ((numbers nil))
             (lambda (lst)
               (setf numbers (nconc numbers lst))
               (/ (apply #'+ numbers) (length numbers)))))
CL-USER> (defvar pants (make-averager))
PANTS
CL-USER> (funcall pants (list 1 2))
3/2
CL-USER> (funcall pants (list 1 2))
3/2
CL-USER> (funcall pants (list 3 4))
13/6
(mapcar #'cadr numbers) can be passed as the argument to APPLY to fit the pair structure.

Just as an experiment, I tried writing a recursive solution that would keep the denominator in an accumulator instead of using LENGTH. My tinkering ended at this...

Code: Select all

(defun avg (lst)
           (labels ((rec (numbers acc)
                      (if (null numbers)
                          0
                          (values (+ (car numbers) (rec (cdr numbers)
                                                        (1+ acc))) acc))))
             (multiple-value-bind (sum den) (rec lst 0)
               (/ sum den))))
Of course, the accumulator returns 0 on the last frame, so the function ends with a divide-by-zero no matter what. Other than putting the accumulator outside the scope of LABELS (which is probably why I used LABELS in the first place) I'm not sure how to deal with this, or if it would be in any way better than just using LENGTH.

Re: averaging

Posted: Tue Aug 31, 2010 9:05 pm
by s-imp
Thanks Kohath.

reduce worked beautifully. And I knew something was wrong when I used 'eval'.

Re: averaging

Posted: Tue Aug 31, 2010 9:19 pm
by s-imp
I like the idea of closures, though it is a bit above my pay-grade at the moment, Duke.

I had another solution that used 'let' and 'dolist', but I didn't want to use setf. It must have been something I read somewhere about minimizing their usage. Is that just for a style of programming using Common Lisp or is there a genuine price for using 'setf'.

(I also should have used cons in my first attempt rather than append, I'm hazarding to guess.)

Re: averaging

Posted: Tue Aug 31, 2010 9:52 pm
by Duke
s-imp wrote:I like the idea of closures, though it is a bit above my pay-grade at the moment, Duke.

I had another solution that used 'let' and 'dolist', but I didn't want to use setf. It must have been something I read somewhere about minimizing their usage. Is that just for a style of programming using Common Lisp or is there a genuine price for using 'setf'.

(I also should have used cons in my first attempt rather than append, I'm hazarding to guess.)
I happened to re-read chapter 3 of Paul Graham's On Lisp this afternoon, and in that very chapter the author opines that programmers should consider non-functional functions (i.e., functions that produce or use side-effects) like SETF to have a "tax" attached to them. You might say the tax is on maintainability and extensibility, which you'll pay in spades when it comes to debugging a larger project, I think.

Incidentally, Graham discusses closures in chapter 5.

Re: averaging

Posted: Wed Sep 01, 2010 3:37 am
by edgar-rft
Now here is a working 'reduce' example:

Code: Select all

(defun average (lst)
  (/ (reduce #'+ lst :key #'(lambda (x) (first (last x))))
     (length lst)))
The :key argument tells 'reduce' to which element from the lists inside 'lst' the '+' function shall be applied. Because 'last' returns the last element as a list, the element itself must be extracted with 'first'.

Also note that '/' often returns ratios, which are more precise than floats, but sometimes the result is not-so obvious:

Code: Select all

(average (list '(a 1) '(b 2) '(c 3) '(d 4) '(e 5) '(f 6)))
=> 7/2
Here '7/2' is the simplified version of '21/6'.

Embarrassing but true, I had to verify this with a commandline calculator...

Warning: using too much Common Lisp can make you lazy in your brain.

Re: averaging

Posted: Wed Sep 01, 2010 5:27 am
by s-imp
:oops: I didn't use the key argument. Thanks for the example. It was very helpful.