Lisp Function that Returns a Sum

2019-07-16 07:13发布

问题:

I am trying to write a weird function, so bear with me here. This function should take a list L as a parameter and have a sum variable. If L is not a list, it should return nil. Otherwise, it should iterate through each element of the list and do the following:

  • If the element is a number and less than zero, it should subtract 1 from the sum.
  • If the element is a number and greater than zero, it should add 1 to the sum.
  • if the element is 0 or not a number, then it should add 0 to the sum.

Here's the code I have, but it returns 0 regardless of arguments passed in:

(defun sigsum (L)
  (let ((sum 0))                   ;;variable sum
  (if (not (listp L))              ;;if L is not a list
      nil                          ;;return nil
      (dotimes (i (length L))      ;;otherwise loop through L
        (if (numberp (elt L i))    ;;if each element is a number
            (if (< (elt L i) 0)    ;;if is less than 0 subtract 1 from sum
                (- sum 1)
            (if (> (elt L i) 0)    ;;if greater than 0 add 1 to sum
                (+ sum 1))
            (+ sum 0))             ;;else add 0 to sum
          (+ sum 0)))              ;;not a number so add 0 to sum
  )
  sum)                             ;;return sum
)

As always, any help is greatly appreciated.

回答1:

(defun discrete (lis)
 (cond
  ((and (listp lis) (not (equal lis nil)))
   (let ((sum 0))
    (loop for item in lis do
     (cond ((or (not (numberp item)) (equal 0 item)) t)
      ((and (numberp item) (> item 1)) (setf sum (+ 1 sum)))
      ((and (numberp item) (< item 1)) (setf sum (- sum 1)))))
    sum))))

Usage:(discrete '(-1 2 3 0 2 2 -1 2 34 0 -1))=> 3

(discrete '(-4 a b))=> -1

(discrete '()) => NIL

(discrete '(a s d f))=> 0



回答2:

Other answers have described the problems in your code, but it might be beneficial to look at other ways of solving the problem. This is a pretty typical case for a reduction with a key function (see reduce). You can sum up the elements in list with (reduce '+ list). However, you don't want to just sum up the elements, some of which might not be numbers, you want to map each element to a number (-1, 0, or 1), and add those up. That means you need a key function. First, let's define the function that takes an element to either -1, 0, or 1:

(defun to-number (x)
  (cond
    ((and (numberp x) (< x 0)) -1)
    ((and (numberp x) (> x 0)) 1)
    ((or (not (numberp x)) (zerop x)) 0)))

Then your sum function needs to return nil if its argument is not a list, or (reduce '+ … :key 'to-number) if its argument is a list:

(defun sum (thing)
  (if (not (listp thing))
      nil
      (reduce '+ thing :key 'to-number)))

Conceptually, this approach is the same as applying the addition operator to the result of (mapcar 'to-number list), but reduce is generally preferred, because there can be a maximum number of arguments a function can be called with, so (apply '+ (mapcar …)) breaks if (mapcar …) returns a list that's longer than that. The other problem is that mapcar will allocate a whole new list to hold the intermediate values (the results of to-number), which is an unnecessary space usage.



回答3:

(- sum 1) does not update any variables. Since you don't use the result it goes away. It's like this is all programming languages. sum + 1 in an Algol language (like C) does not update sum.

If you want to update a variable you can use setf:

(setf sum (+ sum 1))
(setf sum (- sum 1))

Or you can use incf and decf that are macroes that expand to the same as the above expressions.

There are many other ways you can do it in CL. You can use reduce

(defun mysignum (list)
  (if (listp list)
      (reduce (lambda (e acc)
                (+ acc
                   (cond ((or (not (numberp e)) (zerop e)) 0)
                         ((< e 0) -1)   
                         (t 1))))
              list)
       nil))

You can use loop:

(defun mysignum (list)
  (if (listp list)
      (loop :for e :in list
        :when (and (numberp e) (< e 0))
           :summing -1
        :end
        :when (and (numberp e) (> e 0))
           :summing +1
        :end)
       nil))


回答4:

The expression (+ sum 1) returns one more than sum. It does not change sum. You can get where you want using incf or decf, which modify places.

You could also use loop:

(loop :for element :in list
      :count (and (numberp element) (plusp element)) :into plus
      :count (and (numberp element) (minusp element)) :into minus
      :finally (return (- plus minus)))

Signum is also useful, but it has float contagion (if any of the numbers are floats, the result will be a float as well).

(loop :for element :in list
      :when (numberp element)
      :sum (signum element))