3
\$\begingroup\$

I set myself the task to calculate the average of a list, but with two conditions:

  1. negative numbers are ignored
  2. numbers greater than 100 are counted as if they were 100

So the "skewed" average of the list '(1 -3 42 297 14) should be (1 + 42 + 100 + 14) / 4 (157/4).

I wrote 2 functions that do that. Please review and comment.

(defun skewed-average1 (list)
    "calculate average by summing and dividing"
    (let ((sum 0) (n 0))
        (dolist (x list)
            (if (>= x 0)
                (progn
                    (if (> x 100) (setf x 100))
                    (setf sum (+ x sum))
                    (setf n (+ 1 n)))))
         (/ sum n)))

(defun skewed-average2 (list)
    "calculate average by building 'fixed' list"
    (let (newlist)
        (dolist (x list)
            (if (>= x 0)
                (progn
                    (if (> x 100) (setf x 100))
                    (setf newlist (cons x newlist)))))
        (/ (apply #'+ newlist) (length newlist))))

(let ((numbers '(1 -3 42 297 14)))
    (print (skewed-average1 numbers))
    (print (skewed-average2 numbers)))

Also, how should errors be treated? Imagine passing an empty list to the functions; or a list with all negative numbers.

\$\endgroup\$
4
\$\begingroup\$
(defun skewed-average1 (list)
  "calculate average by summing and dividing"
  (let ((sum 0) (n 0))
    (dolist (x list)
      (if (>= x 0)
          (progn
            (if (> x 100) (setf x 100))
            (setf sum (+ x sum))
            (setf n (+ 1 n)))))
    (/ sum n)))

IF ... PROGN is WHEN.

>= 0 is plusp.

(setf sum (+ ... is INCF.

(defun skewed-average1 (list &aux (sum 0) (n 0))
  "calculate average by summing and dividing"
  (dolist (x list (when (plusp n)
                     (/ sum n)))
    (when (plusp x)
      (incf sum (min x 100))
      (incf n))))

Next function:

(setf newlist (cons x newlist)) is (push x newlist).

Don't use APPLY, use REDUCE. APPLY has a list length limit.

Alternative implementations:

(defun skewed-average3 (list)
  (loop for x in list
        when (plusp x)
        sum (min x 100) into sum1
        and count t into count1
        finally (return (when (plusp count1)
                          (/ sum1 count1)))))


(defun skewed-average4 (list)
  (let ((new-list (remove-if #'minusp 
                             (substitute-if 100
                                            (lambda (item) (> item 100))
                                            list))))
    (when new-list
      (/ (reduce #'+ new-list)
         (length new-list)))))
\$\endgroup\$
3
\$\begingroup\$

It has been a while since I did much LISP but here are some things that may help you improve your program.

Avoid using keywords as variable names

In LISP, list is a function and shouldn't be overloaded as a variable name. You might use mylist to make it clear that it's a variable and not the function.

Refactor aggressively

LISP tends to reward the use of compositions of small functions. Instead of large functions that do multiple things, try instead to create small functions that each do one thing and then compose them. For example:

(defun average (mylist)
    (/ (apply #'+ mylist) (length mylist)))

Use mapchar instead of dolist and progn

If you are using progn on a regular basis, it's probably a symptom that you're trying to write LISP using a procedural style. Remember that a list is a fundamental concept in LISP, so there are usually ways to do things with lists without either creating new variables or using progn. For example, let's consider what you're trying to do here. First, you want to remove negative numbers from the list:

(defun remove-neg (mylist)
    (remove-if 'minusp mylist))

Next, we want to "peg" numbers at 100. That is, convert anything greater than 100 into 100:

(defun peg-100 (mylist)
    (mapcar #'(lambda (x) (min x 100)) mylist))

The mapcar function just applies the given function to each item in the list. We create a lambda to express that function, but it could also have been made into a separate function.

The average function was already shown above, so all that's left is to compute the skewed average:

(defun skewed-avg (mylist)
    (average (peg-100 (remove-neg mylist))))

This is likely to perform more slowly than your versions because this version makes multiple passes through the list, but it's more LISPy and if performance becomes a problem, it can often be addressed by using optimize.

Use a condition to handle errors

Error handling in LISP is not unlike exception handling in Java, C++ or Python. It's not complex and is quite flexible. You can read about it in this chapter of an online book. Essentially you define an error condition and then use a handler-case to direct the error to the appropriate handler.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.