Lisp函数计数循环a';名单上有谁

Lisp函数计数循环a';名单上有谁,lisp,common-lisp,Lisp,Common Lisp,我正在尝试编写一个函数,该函数只将列表作为参数,并计算符号a出现在列表中的次数,而不计算列表子列表中的任何a 我对Lisp非常陌生,所以请尽可能使用基本代码,这样我就可以理解它在做什么,即使它效率低下 (defun times (l) (setf x 'a) (cond ((null l) nil) ((equal x (car l)) (+ 1 (times x (cdr L)))) (t (times x(cdr l)))))

我正在尝试编写一个函数,该函数只将列表作为参数,并计算符号
a
出现在列表中的次数,而不计算列表子列表中的任何a

我对Lisp非常陌生,所以请尽可能使用基本代码,这样我就可以理解它在做什么,即使它效率低下

(defun times (l)
    (setf x 'a)
    (cond
        ((null l) nil)
        ((equal x (car l)) (+ 1 (times x (cdr L))))
        (t (times x(cdr l)))))

所以
(times'(ab(a)c))
应该返回1。然而,我得到的错误是,这一行的时间是得到两个参数,而它应该得到一个

这里有一些代码可能会有所帮助。它使用尾部递归并定义一个辅助函数,该函数被递归调用,并跟踪符号“a”随参数计数出现的次数。helper函数接受两个参数,而functino count-a接受一个参数。Count-a使用列表l和它在开始时对符号“a”进行计数的总次数调用帮助程序,该总数为零以启动递归调用

(defun count-a (l)
  (labels ((helper (x count)
             (if (equalp 'a (car x)) (incf count))
             (cond ((null x) count)
                   (t (helper (cdr x) count)))))
     (helper l 0)))
也可以使用循环宏:

(defun count-a-with-a-loop (l)
  (loop for i in l count (equalp 'a i))\
或者正如Coredump指出的那样:

(defun count-a-with-count (l)
   (count 'a l :test #'equal))

注意,equal前面的“#字符让Lisp解释器知道equal是一个函数,称为reader宏

在Common Lisp中有多种实现方法。示例应该足够小,以便您可以遵循(测试它们)

递归实现 您的方法很好,但有一些小错误(除了评论中报告的其他错误外):

  • 不要用于未声明的变量
  • 在基本情况下不要返回
    NIL
    :函数应该返回一个数字
  • 此外,您的代码可以更好地格式化,并且应该使用更长的名称(尤其是小写的
    l
    很难阅读)
以下是修改后的版本:

(defun times (list element)
  (cond
    ((null list) 0)
    ((equal (car list) element) (1+ (times (cdr list) element)))
    (t (times (cdr list) element))))
例子 让我们来看看函数:

CL-USER> (trace times)
(defun times (list element)
  (count element list :test #'equal))
以下是执行跟踪:

CL-USER> (times '(a b c d a f a) 'a)
0: (TIMES (A B C D A F A) A)
  1: (TIMES (B C D A F A) A)
    2: (TIMES (C D A F A) A)
      3: (TIMES (D A F A) A)
        4: (TIMES (A F A) A)
          5: (TIMES (F A) A)
            6: (TIMES (A) A)
              7: (TIMES NIL A)
              7: TIMES returned 0
            6: TIMES returned 1
          5: TIMES returned 1
        4: TIMES returned 2
      3: TIMES returned 2
    2: TIMES returned 2
  1: TIMES returned 2
0: TIMES returned 3
3
您可以看到,列表中访问的每个元素的调用堆栈都在增长。这通常是一种不好的做法,尤其是当递归函数基本上实现循环时

循环 使用一个简单的方法:

或者,使用:

在上面这里,
计数器
是由引入的局部变量。它在循环内随时间递增,只有比较有效。最后,从
dolist
返回
计数器
(第三个参数指示要计算的表单具有结果值)。
dolist
的返回值也是
let
和整个函数的返回值

这也可以通过以下方式重写:

do
中的第一个列表介绍绑定,第二个列表是终止测试(当列表为空时,我们在这里停止),后面是结果表单(这里是计数器)。在循环体内部,我们从输入列表中提取元素并像前面一样进行比较

尾部递归实现 如果要保持递归实现,请添加累加器,并在输入递归求值之前计算所有中间结果。如果所有结果都作为函数参数传递,则无需在递归的每一步跟踪中间结果,这样就无需分配堆栈帧。该语言的规范并没有明确要求能够执行尾部调用消除,但它通常在大多数实现中都可用

(defun times (list element)
  (labels ((recurse (list counter)
             (cond
               ((null list) counter)
               ((equal (first list) element)
                (recurse (rest list) (1+ counter)))
               (t (recurse (rest list) counter)))))
    (recurse list 0)))
在上面这里,
recurse
是由引入的本地递归函数,它接受
计数器
参数。与原始递归函数的不同之处在于,当列表为空时,它返回的是
计数器的当前值,而不是零。在这里,
recurse
的结果总是与递归调用返回的值相同:编译器可以重新绑定输入并执行跳转,而不是分配中间帧

高阶函数 这里还有两种基于高阶函数的方法

首先,使用累加器定义函数的常用方法是with(在其他语言中称为fold)。没有显式突变:

(defun times (list element)
  (reduce (lambda (counter value)
            (if (equal value element)
              (1+ counter)
              counter))
           list
           :initial-value 0))
匿名函数接受累加器的当前状态,即列表中访问的当前值,并应计算累加器的下一个状态(计数器)

或者,使用
nil
第一个参数调用,这样迭代只针对效果进行。表单建立的匿名函数关闭本地
计数器
变量,并可以在比较保持时增加它。它类似于前面的
dolist
示例w.r.t.通过副作用增加计数器,但迭代是通过map隐式完成的

(defun times (list element)
  (let ((counter 0))
    (map () 
         (lambda (value)
           (when (equal value element)
             (incf counter)))
         list)
    counter))
内置 供您参考,有一个内置功能:

CL-USER> (trace times)
(defun times (list element)
  (count element list :test #'equal))

如果使用Lisp编译器(如SBCL),您可能会看到:

* (defun times (l)
    (setf x 'a)
    (cond
      ((null l) nil)
      ((equal x (car l)) (+ 1 (times x (cdr L))))
      (t (times x(cdr l)))))
; in: DEFUN TIMES
;     (TIMES X (CDR L))
; 
; caught WARNING:
;   The function was called with two arguments, but wants exactly one.
; 
; caught WARNING:
;   The function was called with two arguments, but wants exactly one.
; 
; caught WARNING:
;   undefined variable: X
; 
; compilation unit finished
;   Undefined variable:
;     X
;   caught 3 WARNING conditions
Lisp编译器告诉您代码中有三个错误

让我们首先通过引入局部变量
x
,解决未定义变量的问题:

(defun times (l)
  (let ((x 'a))
    (cond
     ((null l) nil)
     ((equal x (car l)) (+ 1 (times x (cdr L))))
     (t (times x (cdr l))))))
现在,我们来看另外两个:您使用两个参数调用
TIMES
。 我们可以删除
x
参数,因为它不是必需的:

(defun times (l)
  (let ((x 'a))
    (cond
     ((null l)            nil)
     ((equal x (car l))   (+ 1 (times (cdr L))))
     (t                   (times (cdr l))))))
搜索更多内容可能更有用,因此我们将
x
添加到参数列表中,并将其添加到调用参数中

(defun times (x l)
  (cond
   ((null l)            nil)
   ((equal x (car l))   (+ 1 (times x (cdr L))))
   (t                   (times x (cdr l)))))
现在,对于空列表,函数应始终返回一个数字,而不是
NIL

(defun times (x l)
  (cond
   ((null l)            0)
   ((equal x (car l))   (+ 1 (times x (cdr L))))
   (t                   (times x (cdr l)))))
由于Lisp具有类似于
first
rest
的功能,我们可以替换
car
cdr

(defun times (x l)
  (cond
   ((null l)             0)
   ((equal x (first l))  (+ 1 (times x (rest l))))
   (t                    (times x (rest l)))))

在else子句的行中,您使用两个参数调用times,x和(cdrl)x是一个未定义的变量。另外,您已经定义了使用一个参数的时间,但是您的两个递归调用都使用了两个参数。嵌套的defun通常是错误的。DEFUN不创建本地函数。使用标签创建局部递归函数。你完全正确。我写了太多Python.nit:
(defun times (x l)
  (cond
   ((null l)             0)
   ((equal x (first l))  (+ 1 (times x (rest l))))
   (t                    (times x (rest l)))))