averaging

Discussion of Common Lisp
burton
Posts: 10
Joined: Sat Aug 28, 2010 6:05 pm

averaging

Post by burton » Tue Aug 31, 2010 4:32 pm

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

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: averaging

Post by edgar-rft » Tue Aug 31, 2010 5:44 pm

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.

s-imp
Posts: 11
Joined: Sat Aug 28, 2010 5:04 am

Re: averaging

Post by s-imp » Tue Aug 31, 2010 6:48 pm

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)))


Kohath
Posts: 61
Joined: Mon Jul 07, 2008 8:06 pm
Location: Toowoomba, Queensland, Australia
Contact:

Re: averaging

Post by Kohath » Tue Aug 31, 2010 7:26 pm

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.

Duke
Posts: 38
Joined: Sat Oct 17, 2009 10:40 pm
Contact:

Re: averaging

Post by Duke » Tue Aug 31, 2010 8:05 pm

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.
"If you want to improve, be content to be thought foolish and stupid." -Epictetus

s-imp
Posts: 11
Joined: Sat Aug 28, 2010 5:04 am

Re: averaging

Post by s-imp » Tue Aug 31, 2010 9:05 pm

Thanks Kohath.

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

s-imp
Posts: 11
Joined: Sat Aug 28, 2010 5:04 am

Re: averaging

Post by s-imp » Tue Aug 31, 2010 9:19 pm

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.)

Duke
Posts: 38
Joined: Sat Oct 17, 2009 10:40 pm
Contact:

Re: averaging

Post by Duke » Tue Aug 31, 2010 9:52 pm

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.
"If you want to improve, be content to be thought foolish and stupid." -Epictetus

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: averaging

Post by edgar-rft » Wed Sep 01, 2010 3:37 am

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.

s-imp
Posts: 11
Joined: Sat Aug 28, 2010 5:04 am

Re: averaging

Post by s-imp » Wed Sep 01, 2010 5:27 am

:oops: I didn't use the key argument. Thanks for the example. It was very helpful.

Post Reply