在 lisp 中平均使用 &rest

Average using &rest in lisp

所以我被要求做一个函数 i LISP 来计算任何给定数字的平均值。我被要求这样做的方式是使用 &rest 参数。所以我想到了这个:

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

现在我知道这是不正确的,因为 (cdr b) returns 一个列表,里面有一个列表,所以当我做 (car b) 它从来没有 returns 一个原子,所以它永远不会添加(+)

这是我的第一个问题:

现在还有一件事: 当我 运行 这个函数并给 &rest 赋值时,比如(平均 1 2 3 4 5)它给我 Whosebug 错误。我跟踪了这​​个函数,发现它卡在了一个循环中,总是调用带有 (cdr b) 的函数是空的,所以它在那里循环。 我的问题是:

编辑:我知道这个函数只做 + 操作,我知道我必须除以 b 列表的长度 + 1,但是因为我得到这个错误我想先解决它。

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

当您使用 (average 1 2 3 4) 调用它时,在函数内部,符号 a 将绑定到 1,而符号 b 将绑定到 proper list (2 3 4).

因此,在 average(car b) 中, 将给您第一个剩余参数,而 (cdr b) 将给您其余参数其余参数。

但是当你递归地调用 (average a (cdr b)) 时,你只用 两个 参数调用它,不管首先给函数提供了多少参数.在我们的示例中,它与 (average 1 '(3 4)).

相同

更重要的是,第二个参数现在是一个列表。因此,在对 average 的第二次调用中,符号将按如下方式绑定:

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

b 是一个只有一个元素的列表:另一个列表。这就是为什么将 (car b) 作为参数传递给 +.

时会出现错误的原因

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 Whosebug 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) 仅当 b 为空列表时才为真。但是当你调用 (average a '()) 时,b 将绑定到 (()),这是一个包含空列表的列表。

可以使用 apply 解决在以下调用中只传递两个参数的问题:它需要函数以及参数列表来调用它:(appply #'average (cons a (cdr b)))

现在开始编写平均函数的最初目标:计算平均值包含两个任务:

  1. 计算所有元素的总和。
  2. 将其除以所有元素的数量。

您可以编写自己的函数来递归地添加所有元素来解决第一部分(做吧!),但是已经有这样的函数:

(+ 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

因此你的平均值就是

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

最后一点:在处理列表时,您不应该使用 carcdr。最好使用更具描述性的名称 firstrest.


如果性能对您很重要,最好折叠参数(使用可能会优化的 reduce):

(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)

#' 是一个 reader 宏,特别是 standard dispatching macro characters 之一,因此是 (function ...)

的缩写

只需定义 average*,它调用通常的 average 函数。

(defun average* (&rest numbers)
  (average numbers))

我认为 Rainer Joswig 的回答是非常好的建议:首先定义一个采用简单列表参数的版本,然后根据它定义 &rest 版本会更容易。不过,这是提及 spreadable arglists 的好机会。它们是一种很好的技术,可以使您的库代码更易于使用。

在最常见的形式中,Common Lisp 函数 apply 接受一个函数指示符和一个参数列表。例如,您可以

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

不过,如果您查看文档,apply 实际上接受一个可展开的 arglist 指示符作为 &rest 参数。这是一个列表,其最后一个元素必须是一个列表,并且表示列表中除最后一个元素之外的所有元素的列表,后面是该最终列表中的所有元素。例如,

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

因为 spreadable arglist 是 (1 (2)),所以实际参数是 (1 2)。编写实用程序到 unspread 可扩展的 arglist 指示符很容易:

(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)

现在您可以编写一个 average* 函数,它采用其中之一(除其他外,它让您获得行为,就像 apply,你可以传递一个简单的列表):

(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..

现在您可以将 average 编写为一个函数,该函数接受 &rest 参数并将其传递给 average*:

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

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

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