0

So i was asked to do a function i LISP that calculates the average of any given numbers. The way i was asked to do this was by using the &rest parameter. so i came up with this :

(defun average (a &rest b)
   (cond ((null a) nil)
         ((null b) a)
         (t (+ (car b) (average a (cdr b))))))

Now i know this is incorrect because the (cdr b) returns a list with a list inside so when i do (car b) it never returns an atom and so it never adds (+)

And that is my first question:

  • How can i call the CDR of a &rest parameter and get only one list instead of a list inside a list ?

Now there is other thing : When i run this function and give values to the &rest, say (average 1 2 3 4 5) it gives me stackoverflow error. I traced the funcion and i saw that it was stuck in a loop, always calling the function with the (cdr b) witch is null and so it loops there. My question is:

  • If i have a stopping condition: ( (null b) a) , shouldnt the program stop when b is null and add "a" to the + operation ? why does it start an infinite loop ?

EDIT: I know the function only does the + operation, i know i have to divide by the length of the b list + 1, but since i got this error i'd like to solve it first.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Pedro Costa
  • 427
  • 3
  • 13
  • 31

3 Answers3

3
(defun average (a &rest b)
  ; ...
  )

When you call this with (average 1 2 3 4) then inside the function the symbol a will be bound to 1 and the symbol b to the proper list (2 3 4).

So, inside average, (car b) will give you the first of the rest parameters, and (cdr b) will give you the rest of the rest parameters.

But when you then recursively call (average a (cdr b)), then you call it with only two arguments, no matter how many parameters where given to the function in the first place. In our example, it's the same as (average 1 '(3 4)).

More importantly, the second argument is now a list. Thus, in the second call to average, the symbols will be bound as follows:

  • a = 1
  • b = ((3 4))

b is a list with only a single element: Another list. This is why you'll get an error when passing (car b) as argument to +.

Now there is other thing : When i run this function and give values to the &rest, say (average 1 2 3 4 5) it gives me stackoverflow error. I traced the funcion and i saw that it was stuck in a loop, always calling the function with the (cdr b) witch is null and so it loops there. My question is:

If i have a stopping condition: ( (null b) a) , shouldnt the program stop when b is null and add "a" to the + operation ? why does it start an infinite loop ?

(null b) will only be truthy when b is the empty list. But when you call (average a '()), then b will be bound to (()), that is a list containing the empty list.

Solving the issue that you only pass exactly two arguments on the following calls can be done with apply: It takes the function as well as a list of parameters to call it with: (appply #'average (cons a (cdr b)))

Now tackling your original goal of writing an average function: Computing the average consists of two tasks:

  1. Compute the sum of all elements.
  2. Divide that with the number of all elements.

You could write your own function to recursively add all elements to solve the first part (do it!), but there's already such a function:

(+ 1 2) ; Sum of two elements
(+ 1 2 3) ; Sum of three elements
(apply #'+ '(1 2 3)) ; same as above
(apply #'+ some-list) ; Summing up all elements from some-list

Thus your average is simply

(defun average (&rest parameters)
  (if parameters  ; don't divide by 0 on empty list
      (/ (apply #'+ parameters) (length parameters))
      0))

As a final note: You shouldn't use car and cdr when working with lists. Better use the more descriptive names first and rest.


If performance is critical to you, it's probably best to fold the parameters (using reduce which might be optimized):

(defun average (&rest parameters)
    (if parameters
        (let ((accum
                (reduce #'(lambda (state value)
                            (list (+ (first state) value) ;; using setf is probably even better, performance wise.
                                (1+ (second state))))
                        parameters
                        :initial-value (list 0 0))))
            (/ (first accum) (second accum)))
        0))

(Live demo)

#' is a reader macro, specifically one of the standard dispatching macro characters, and as such an abbreviation for (function ...)

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • Thank you for your time and answer ! I wasnt aware of the Apply function, so the way you did that last function it isnt even a recursive one ! Pretty simple now that i know how it is. Im gonna study that apply a bit more. – Pedro Costa Nov 06 '15 at 12:15
  • May i just ask one more question ? What is the # symbol for ? – Pedro Costa Nov 06 '15 at 12:28
  • Note that taking the sum and then the length requires traversing the list *twice*. While it expresses the intent clearly, it would be more efficient to compute the length while computing the sum. – Joshua Taylor Nov 06 '15 at 13:02
  • 1
    @PedroCosta `#'x` is shorthand for `(function x)`, which uses the [special operator called **function**](http://www.lispworks.com/documentation/HyperSpec/Body/s_fn.htm). – Joshua Taylor Nov 06 '15 at 13:05
  • `(/ (reduce #'+ list) (length list))` is just as fast but does no consing. `(loop :for e :in list :sum e :into sum :count e :into count :finally (return (/ sum count)))` is much faster. – Svante Nov 06 '15 at 21:17
3

Just define average*, which calls the usual average function.

(defun average* (&rest numbers)
  (average numbers))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • I'd actually suggest doing this the other way round, where `average*` takes a list or takes a spreadable arglist (like apply does), and `average` has the `&rest` argument. Then `average` and `average*` behave like `list` and `list*`. – Joshua Taylor Nov 05 '15 at 19:51
  • @JoshuaTaylor: I don't think list/sequence operations should use the naming of `list`/`list*`. The default operation should be over lists/sequences and use the naming without `*`. – Rainer Joswig Nov 05 '15 at 19:58
  • @JoshuaTaylor: The default operations should be efficient and not constrained by number of argument limits. – Rainer Joswig Nov 05 '15 at 20:08
  • I entirely agree about the argument limit issue. The only overhead in an approach like what I used in [my answer](http://stackoverflow.com/a/33553757/1281433) is getting the actual arglist from the spreadable arglist designator. Yes, it means that someone could run into the arguments limit by doing something like `(average 1 2 3 ... 999 1000 '())`, but the caller probably should have bundled those up beforehand so as to write `(average '(1 2 3 ... 999 1000))` anyhow. – Joshua Taylor Nov 05 '15 at 20:12
  • well this is pretty advanced for my knowledge of LISP at this time :P but thank you for the advice anyway ! – Pedro Costa Nov 06 '15 at 12:17
2

I think that Rainer Joswig's answer is pretty good advice: it's easier to first define a version that takes a simple list argument, and then define the &rest version in terms of it. This is a nice opportunity to mention spreadable arglists, though. They're a nice technique that can make your library code more convenient to use.

In most common form, the Common Lisp function apply takes a function designator and a list of arguments. You can do, for instance,

(apply 'cons '(1 2))
;;=> (1 . 2)

If you check the docs, though, apply actually accepts a spreadable arglist designator as an &rest argument. That's a list whose last element must be a list, and that represents a list of all the elements of the list except the last followed by all the elements in that final list. E.g.,

(apply 'cons 1 '(2))
;;=> (1 . 2)

because the spreadable arglist is (1 (2)), so the actual arguments (1 2). It's easy to write a utility to unspread a spreadable arglist designator:

(defun unspread-arglist (spread-arglist)
  (reduce 'cons spread-arglist :from-end t))

(unspread-arglist '(1 2 3 (4 5 6)))
;;=> (1 2 3 4 5 6)

(unspread-arglist '((1 2 3)))
;;=> (1 2 3)

Now you can write an average* function that takes one of those (which, among other things, gets you the behavior, just like with apply, that you can pass a plain list):

(defun %average (args)
  "Returns the average of a list of numbers."
  (do ((sum 0 (+ sum (pop args)))
       (length 0 (1+ length)))
      ((endp args) (/ sum length))))

(defun average* (&rest spreadable-arglist)
  (%average (unspread-arglist spreadable-arglist)))

(float (average* 1 2 '(5 5)))
;;=> 3.25

(float (average* '(1 2 5)))
;;=> 2.66..

Now you can write average as a function that takes a &rest argument and just passes it to average*:

(defun average (&rest args)
  (average* args))

(float (average 1 2 5 5))
;;=> 3.5

(float (average 1 2 5))
;;=> 2.66..
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • thank you for you time and answer ! im pretty much a beginner in LISP so i don't yet understand some of the concepts you talked about. But i understand how to make a simple Average function now. – Pedro Costa Nov 06 '15 at 12:19
  • @PedroCosta Yes, the `%average` function is the version that takes a list, and then you can define a very simple `average` that takes `&rest` args with `(defun average (&rest args) (%average args))`. That's the kind of approach that RainerJoswig was suggesting (and that I agree with, too), though a slightly different naming convention. – Joshua Taylor Nov 06 '15 at 12:59