所以我被要求做一个LISP函数来计算任何给定数字的平均值。我被要求这样做的方法是使用& rest参数。所以我想出了这个:
(defun average (a &rest b)
(cond ((null a) nil)
((null b) a)
(t (+ (car b) (average a (cdr b))))))
现在我知道这是不正确的,因为(cdr b)返回一个列表里面有一个列表所以当我这样做(车b)它永远不会返回一个原子,所以它永远不会添加(+)
这是我的第一个问题:
现在还有其他的事情: 当我运行这个函数并给& rest赋值时,说(平均1 2 3 4 5)它给我stackoverflow错误。我追踪了这个函数,我发现它被卡在一个循环中,总是用(cdr b)调用函数,巫婆是null,所以它在那里循环。 我的问题是:
答案 0 :(得分:3)
(defun average (a &rest b)
; ...
)
当您使用(average 1 2 3 4)
进行调用时,在函数内部,符号a
将绑定到1
,符号b
将绑定到proper list {{ 1}}。
因此,在(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)
时会出现错误。
当现在还有其他的事情:当我运行这个函数并给& rest值时,比如说(平均1 2 3 4 5)它给了我stackoverflow错误。我追踪了这个函数,我发现它被卡在一个循环中,总是用(cdr b)调用函数,巫婆是null,所以它在那里循环。我的问题是:
如果我有一个停止条件:((null b)a),当b为空时程序停止并且+操作加“a”?为什么它会开始无限循环?
+
为空列表时, (null b)
才会失真。但是当您拨打b
时,(average a '())
将绑定到b
,这是一个包含空列表的列表。
解决以下调用只传递两个参数的问题可以使用apply
完成:它需要函数以及参数列表来调用它:(())
现在解决编写平均函数的最初目标:计算平均值包括两个任务:
你可以编写自己的函数来递归地添加所有元素来解决第一部分(做它!),但是已经有了这样一个函数:
(appply #'average (cons a (cdr b)))
因此你的平均值只是
(+ 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))
和car
。更好地使用更具描述性的名称cdr
和first
。
如果性能对您至关重要,最好折叠参数(使用可能优化的reduce
):
rest
(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))
是一个读者宏,特别是s tandard dispatching macro characters之一,因此是#'
答案 1 :(得分:2)
只需定义average*
,即调用通常的average
函数。
(defun average* (&rest numbers)
(average numbers))
答案 2 :(得分:2)
我认为Rainer Joswig的回答是非常好的建议:首先定义一个采用简单列表参数的版本,然后根据它定义& rest版本会更容易。不过,这是提及可扩展的arglists 的好机会。它们是一种很好的技术,可以使您的库代码更方便使用。
在最常见的形式中,Common Lisp函数 apply 采用函数指示符和参数列表。例如,你可以做到
(apply 'cons '(1 2))
;;=> (1 . 2)
但是,如果您检查文档,apply实际上接受一个可扩展的arglist指示符作为& rest参数。这是一个列表,其最后一个元素必须是一个列表,它表示列表中除列表之外的所有元素的列表,后面跟着该最终列表中的所有元素。例如,
(apply 'cons 1 '(2))
;;=> (1 . 2)
因为可扩展的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)
现在你可以编写一个平均* 函数,其中包含其中一个(除其他外,它会让你行为,就像 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..
现在你可以将平均值写成一个带有& rest参数的函数,然后将其传递给 average * :
(defun average (&rest args)
(average* args))
(float (average 1 2 5 5))
;;=> 3.5
(float (average 1 2 5))
;;=> 2.66..