Lisp函数计数在列表中重复出现

时间:2018-02-16 02:27:56

标签: lisp common-lisp

我正在尝试编写一个仅将列表作为参数的函数,并计算符号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.但是我得到的错误是,当这个行时间得到两个参数时应该得到一个。

3 个答案:

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

上面,counterLET引入的局部变量。它在循环内增加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具有firstrest等功能,我们可以替换carcdr

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