我正在尝试编写一个仅将列表作为参数的函数,并计算符号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 '(a b (a) c))
应该返回1.但是我得到的错误是,当这个行时间得到两个参数时应该得到一个。
答案 0 :(得分:3)
在Common Lisp中有多种方法可以实现这一点。该示例应足够小,以便您遵循(测试它们)。
您的方法很好,除了您有小错误(除了评论中报告的其他错误):
SETF
用于未展开的变量。NIL
:您的函数应返回一个数字。l
很难阅读)以下是修改后的版本:
(defun times (list element)
(cond
((null list) 0)
((equal (car list) element) (1+ (times (cdr list) element)))
(t (times (cdr list) element))))
让TRACE
函数:
CL-USER> (trace times)
这是执行追踪:
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
您可以看到调用堆栈对于列表中访问的每个元素都会增长。这通常是一种不好的做法,特别是当递归函数基本上实现循环时。
使用简单的LOOP
:
(defun times (list element)
(loop for value in list count (equal value element)))
或者,使用DOLIST
:
(defun times (list element)
(let ((counter 0))
(dolist (value list counter)
(when (equal element value)
(incf counter)))))
上面,counter
是LET
引入的局部变量。它在循环内增加INCF
,只有WHEN
比较成立。最后,从counter
返回dolist
(第三个参数指示要评估哪个表单具有结果值)。 dolist
的返回值也是let
和整个函数的返回值。
这也可以使用DO
重写:
(defun times (list element)
(do ((counter 0)) ((null list) counter)
(when (equal element (pop list))
(incf counter))))
do
中的第一个列表引入了绑定,第二个列表是终止测试(此处我们在列表为空时停止),后跟结果表单(此处为计数器)。在循环体内,我们从输入列表中POP
个元素并像以前一样进行比较。
如果要保留递归实现,请在进入递归评估之前添加累加器并计算所有中间结果。如果所有结果都作为函数参数传递,则无需在递归的每个步骤中跟踪中间结果,这样就无需分配堆栈帧。语言规范并未明确要求执行尾部调用消除的能力,但通常在大多数实现中都可以使用。
(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
是由LABELS
引入的本地递归函数,它接受counter
参数。与原始递归函数的不同之处在于,当列表为空时,它返回当前值counter
而不是零。这里,recurse
的结果总是与递归调用返回的值相同:编译器只需重新绑定输入并执行跳转而不是分配中间帧。
以下是另外两种基于高阶函数的方法。
首先,使用累加器定义函数的常用方法是使用REDUCE
(在其他语言中称为 fold )。没有明显的突变:
(defun times (list element)
(reduce (lambda (counter value)
(if (equal value element)
(1+ counter)
counter))
list
:initial-value 0))
匿名函数接受累加器的当前状态,列表中访问的当前值,并计算累加器的下一个状态(计数器)。
或者,使用nil
第一个参数调用MAP
,以便仅对效果进行迭代。由LAMBDA
形式建立的匿名函数关闭本地counter
变量,并且在比较成立时可以递增它。它类似于之前的dolist
示例w.r.t.通过副作用递增计数器,但迭代是使用map隐式完成的。
(defun times (list element)
(let ((counter 0))
(map ()
(lambda (value)
(when (equal value element)
(incf counter)))
list)
counter))
有关您的信息,有一个内置的COUNT
功能:
(defun times (list element)
(count element list :test #'equal))
答案 1 :(得分:1)
以下是一些可能有用的代码。它使用尾递归并定义一个辅助函数,该函数以递归方式调用,并跟踪符号' a出现的次数与参数计数。辅助函数有两个参数,但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))
注意等于之前的'#字符让Lisp解释器知道equal是一个函数,称为读取器宏。
答案 2 :(得分:0)
如果您使用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)))))