在 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 参数的 CDR 并只获取一个列表而不是列表中的列表?
现在还有一件事:
当我 运行 这个函数并给 &rest 赋值时,比如(平均 1 2 3 4 5)它给我 Whosebug 错误。我跟踪了这个函数,发现它卡在了一个循环中,总是调用带有 (cdr b) 的函数是空的,所以它在那里循环。
我的问题是:
- 如果我有一个停止条件:( (null b) a) ,程序不应该在 b 为 null 时停止并将 "a" 添加到 + 操作吗?为什么它会开始无限循环?
编辑:我知道这个函数只做 + 操作,我知道我必须除以 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) ; 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
和 rest
.
如果性能对您很重要,最好折叠参数(使用可能会优化的 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))
#'
是一个 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..
所以我被要求做一个函数 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 参数的 CDR 并只获取一个列表而不是列表中的列表?
现在还有一件事: 当我 运行 这个函数并给 &rest 赋值时,比如(平均 1 2 3 4 5)它给我 Whosebug 错误。我跟踪了这个函数,发现它卡在了一个循环中,总是调用带有 (cdr b) 的函数是空的,所以它在那里循环。 我的问题是:
- 如果我有一个停止条件:( (null b) a) ,程序不应该在 b 为 null 时停止并将 "a" 添加到 + 操作吗?为什么它会开始无限循环?
编辑:我知道这个函数只做 + 操作,我知道我必须除以 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) ; 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
和 rest
.
如果性能对您很重要,最好折叠参数(使用可能会优化的 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))
#'
是一个 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..