在lisp中平均使用和休息

时间:2015-11-05 17:09:08

标签: common-lisp average

所以我被要求做一个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参数的CDR并只获取一个列表而不是列表中的列表?

现在还有其他的事情: 当我运行这个函数并给& rest赋值时,说(平均1 2 3 4 5)它给我stackoverflow错误。我追踪了这个函数,我发现它被卡在一个循环中,总是用(cdr b)调用函数,巫婆是null,所以它在那里循环。 我的问题是:

  • 如果我有一个停止条件:((null b)a),当b为空时程序停止并且添加" a"到+操作?为什么它会开始无限循环?
编辑:我知道该函数只执行+操作,我知道我必须除以b列表的长度+ 1,但由于我得到了这个错误,我想先解决它。

3 个答案:

答案 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完成:它需要函数以及参数列表来调用它:(())

现在解决编写平均函数的最初目标:计算平均值包括两个任务:

  1. 计算所有元素的总和。
  2. 将其除以所有元素的数量。
  3. 你可以编写自己的函数来递归地添加所有元素来解决第一部分(做它!),但是已经有了这样一个函数:

    (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。更好地使用更具描述性的名称cdrfirst

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

    rest

    (Live demo)

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