Still moving. Wrote this the other day, not sure why - found some better Java tutorial code to use as exercises.
public class ScoresAverage {
public static void main(String[] args)
{
double scores[] = { 76.0, 84.5, 92.5, 88.0, 96.0 };
double sum= 0;
double average;
for (int i = 0; i < scores.length; i++)
{
sum += scores[i];
}
average = sum / scores.length;
System.out.println("The average of the scores is " + average);
System.out.println(scores[4] + " is the fourth element in the scores array.");
System.out.println("There are " + scores.length + " elements in this array.");
}
}
Working with lists is more convenient than working with arrays in Lisp. But, in Lisp, things that aren't easy can usually be made to be easy pretty easily. Yes, I know, that's a lot of easy - welcome to Lisp! For this half-scale, I wrote a couple of functions to make processing one-dimensional arrays (simple-vector) a little easier.
The whole purpose of this scale is to calculate the average of a set of values in an array. Three different approaches are presented.
- An exact knockoff of the Java code.
- A lispier version that uses a couple of functions - coerce (builtin) and avg (custom)
- A version that uses the array without consing. Create some functions/macro that can be used as general-purpose utilities: sv+, dosvref, svactive and svavg.
Paul Graham, in OnLisp, describes when to write a function or macro. He also describes when a function might benefit from being written as a macro. In the case of avg he points out, "Consider for example the operator avg, which returns the average of its arguments. It could be defined as a function but there is a good case for defining it as a macro, because the function version would entail an unnecessary call to length each time avg was called. At compile-time we may not know the values of the arguments, but we do know how many there are, so the call to length could just as well be made then."
Link: http://www.bookshelf.jp/texi/onlisp/onlisp_9.html#SEC63
The two versions he presents are as follows. Since this code will call apply on
avg, it'll use the function version (since you can't call funcall or apply on a macro):
(defun avg (&rest args)
(/ (apply #'+ args) (length args)))
(defmacro avg (&rest args)
`(/ (+ ,@args) ,(length args)))
This first Lisp version mirrors the Java code above. The #() syntax creates and populates an array.
(let* ((scores-array #(76.0 84.5 92.5 88.0 96.0))
(total-score 0))
;; Mirror the Java code and iterate to calculate the total score. Can call 'loop or 'dotimes.
(loop for x from 0 below (array-total-size scores-array)
do (setf total-score (+ total-score (svref scores-array x))))
(format t "The average of the scores is ~F~%" (/ total-score (array-total-size scores-array)))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))
The
format expression ~:R will output a number as '(first second third fourth ...). The
format function in Lisp is comprehensive.
This next version converts the array to a list via the
coerce call. The resulting list is passed to the function
avg. Works, but it conses, which is generally a bad thing if it can be avoided.
(let* ((scores-array #(76.0 84.5 92.5 88.0 96.0)))
(format t "The average of the scores is ~F~%" (apply #'avg (coerce scores-array 'list)))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))
A couple of array utilities.
dosvref is a macro that provides iteration over a simple vector.
sv+ sums the entries in a simple-vector (array).
svavg is the array-equivalent function for avg above.
(defmacro dosvref ((val array) &body body)
"dolist equivalent for simple vector. Iterate over array elements up to
either fill-pointer (if there is a fill-pointer) or array-total-size."
(let ((i (gensym)))
`(dotimes (,i (if (array-has-fill-pointer-p ,array)
(fill-pointer ,array)
(array-total-size ,array)))
;; bind val for use in body
(let ((,val (aref ,array ,i)))
,@body))))
(defun sv+ (array)
"Sum the values of the elements of array."
(let ((acc 0))
(dosvref (v array)
(setf acc (+ acc v)))
acc))
(defun svactive (array)
"Assume data in array is contiguous starting at position zero.
Either return the fill-pointer or an equivalent, calculated, value for the last,
non-nil value in the array. This would be more efficient using a
divide-and-conquer approach."
(if (array-has-fill-pointer-p array)
(fill-pointer array)
(let ((cnt (array-total-size array)))
(loop for i from (1- (array-total-size array)) downto 0
when (not (svref array i))
do (decf cnt)
else return cnt))))
(defun svavg (array)
"Avoid consing up a new list (eg. by calling coerce). Return the avg."
(/ (sv+ array) (svactive array)))
This last version of the equivalent Java code uses
svavg to avoid consing up a new list.
(let ((scores-array #(76.0 84.5 92.5 88.0 96.0)))
(format t "The average of the scores is ~S~%" (svavg scores-array))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))
The output:
The average of the scores is 87.4
96.0 is the fourth element in the scores-array.
There are 5 elements in the scores-array.