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)))))