如何在Lisp Works中对包含数字和字母的列表求和

时间:2016-10-05 17:57:13

标签: lisp common-lisp clisp

我正在尝试定义一个以列表作为参数的和函数。问题是我的列表不仅包含数字,还包含字母。所以我的问题是,我如何避免这些字母并继续验证列表的其余部分?

例子:(总和'(a 2 4 b d))= 6

(defun sum (l) 
    ( if (numberp (CAR l)) (sum (CDR l)) (+ (CAR l) (sum (CDR l))) )
)

我所拥有的只是一个"堆栈溢出"错误。

提前致谢。

2 个答案:

答案 0 :(得分:1)

您的代码尝试应用递归方法。格式正确,名称较长:

(defun sum (list) 
  (if (numberp (car list))
      (sum (cdr list))
      (+ (car list) (sum (cdr list)))))

您有堆栈溢出,因为当列表为空时您没有提供基本情况。 如果你将NIL列表传递给你的函数,会发生什么?

  1. (car list)返回NIL,这不是数字。
  2. 你去了其他分店。
  3. 您尝试将(car list)(仍然为NIL)添加到递归调用SUM ,其中(cdr list)也是 NIL。
  4. 您将在递归调用中返回第一步:无限递归,最终会触发错误,因为您会在每次调用时不断分配堆栈帧。
  5. 所以,你必须定义给出空列表时会发生什么。 这是典型的家庭作业问题。我最喜欢的模板涉及etypecase,因为它提供了一种防御性编码方法,可以尽早拒绝错误并且对读者非常友好:

    (defun foo (list)
      (etypecase list
        (null <base-case>)
        (cons <general-case>)))
    

    但是,您经常会发现这个或类似于cond的内容:

    (defun foo (list)
      (if (null list)
          <base-case>
          <general-case>))
    

    这可能是学生的期望。更好的方法是使用endp而不是null,因为前者检查参数是否实际上是一个列表。

    在您的问题中:

    • 基本情况显然会返回0.
    • 一般情况计算car(零,如果不是数字)与递归获得的cdr之和的总和。

    但是,您将拥有一个需要在调用堆栈上存储中间结果的递归函数,这很浪费。为了得到一个尾递归函数,你需要将得到的和作为函数的一个参数传递:首先传递0,然后每个递归调用首先计算总和,然后递归调用自身。基本案例返回总和。这可以保证在递归调用之间不需要存储中间结果,在适当的情况下(这取决于您的实现)可以作为循环进行优化。但是由于Common Lisp已经提供了迭代结构,你应该在实践中使用它们:

    (loop for e in list when (numberp e) sum e)
    

    如果您对高阶函数感兴趣,以下工作也可以,但是分配一个中间列表(这是否可接受取决于列表的预期大小):

    (reduce #'+ (remove-if-not #'numberp list))
    

答案 1 :(得分:0)

不是最有效的方法,但检查函数remove-if(或delete-if)和numberp。