(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:
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:
- Compute the sum of all elements.
- 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 ...)