【问题标题】:Lisp function count recurring a's in listLisp函数计数列表中重复出现的a
【发布时间】:2018-07-26 22:05:15
【问题描述】:

我正在尝试编写一个函数,它只接受一个列表作为参数,并计算符号 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 '(a b (a) c)) 应该返回 1。但是我得到的错误是,当它应该得到一个时,这个行时间得到了两个参数。

【问题讨论】:

  • 在 else 子句中,您使用两个参数调用时间,x 和 (cdr l)
  • X 是一个未定义的变量。另外,您已将 TIMES 定义为具有一个参数,但是您的两个递归调用都使用两个参数..

标签: lisp common-lisp


【解决方案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))

注意 equal 之前的 '# 字符让 Lisp 解释器知道 equal 是一个函数,称为读取器宏。

【讨论】:

  • 嵌套的 DEFUNS 通常是错误的。 DEFUN 不创建局部函数。使用 LABELS 创建本地递归函数。
  • 你完全正确。我写的 Python 太多了。
【解决方案2】:

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

递归实现

您的方法很好,除了您有小错误(除了 cmets 中报告的其他错误):

  • 不要将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 比较成立。最后,counterdolist 中返回(第三个参数表示要评估哪个表单具有结果值)。 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)))

这里recurseLABELS引入的局部递归函数,它接受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))

【讨论】:

  • nit:我不认为你需要:test #'equal 位在最后;默认检查 (eq) 应该在这里工作,因为您使用的是符号。 (在线示例:rextester.com/OUH43086
  • @agam 是的(轮到我了:默认测试是eql ;-))我将编写一个与其他示例类似的函数以避免任何混淆。
  • 啊(隐藏)你是对的! (我应该停止说明确的:test 是不必要的)
【解决方案3】:

如果您使用 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)))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-16
    • 1970-01-01
    • 2021-02-05
    • 2015-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多