【问题标题】:How to return from a recursive LISP function?如何从递归 LISP 函数返回?
【发布时间】:2014-10-08 12:17:09
【问题描述】:

我是一个新的 lisp 程序员,我正在尝试创建我的第一个递归 lisp 函数来返回两个列表中较大的一个。每次我运行该功能时,它似乎都会崩溃,因为我猜我的基本情况没有得到满足。这是我的代码。

(defun longest (l1 l2)
  (cond ((null l1) (l2))
        ((null l2) (l1))
        (t (rest l1) 
           (rest l2) 
           (longest l1 l2))
  )
)

我知道这不适用于两个列表长度相同的情况,但我只是想让一些东西先工作。我知道这也可以通过 while 循环来实现,但出于学习的目的,我想使用递归。

为什么这段代码永远不会终止?

【问题讨论】:

    标签: recursion lisp


    【解决方案1】:

    此代码永远不会终止,因为您一次又一次地使用相同的输入数据调用longestrest 返回列表中除第一个之外的所有元素,不修改列表,最初作为参数传递。而且您还有其他错误:

    (defun longest (l1 l2)
      (cond ((null l1) (l2)) ;; <= will give error,
            ((null l2) (l1)) ;;  because there is no such function like l2
            (t (rest l1) ;; <=  result ignored
               (rest l2) ;; <=  result ignored
               (longest l1 l2)))) ;; <= called with the same l1 and l2
    

    显然,它会一直运行,直到您用完堆栈为止。预期行为的正确代码是:

    (defun longest (l1 l2)
      (cond ((null l1) l2)
            ((null l2) l1)
            (t 
             (longest (rest  l1) (rest  l2)))))
    

    请注意,您可能无法通过这种方法获得预期结果,因为 longest 每次都会获取列表的 CDR,因此它只会返回最长列表的其余元素,因此如果您调用 @987654327 @你只会得到'(3),这可能不是你所期望的。

    如果你想让它真正返回所有最长的列表,你必须修改你的代码。

    【讨论】:

    • 这很有意义,我犯了愚蠢的错误,但这是可以预料的,因为我是这种语言的新手。非常感谢我的朋友。
    • @Jake,请注意,您可能无法做您想做的事。请参阅编辑后的答案。
    • 啊,是的,好点,第一次运行该函数时,我必须将两个列表都存储在某种全局变量中?
    • @Jake,我只是想知道,你不能从其他列表中减去长度吗?
    • 是的,但这不会利用递归,我正在做的问题将帮助我理解 lisp 中的递归,诚然,对这个问题使用递归只会使问题更加复杂:)。
    【解决方案2】:

    递归的一个有用技巧是构建一个查找关键信息的本地“worker”函数。 工人的结果然后被适当地“处理”以给出您需要的最终答案。这类似于命令式语言中经常使用迭代的方式,但更加灵活。

    例如,在您的示例中,解决此问题的一个好方法是:

    (defun longest (l1 l2)
      (labels ((first-longer (a b)
          (cond ((null a) nil)
                ((null b) t)
                (t (first-longer (rest a) (rest b))))))
        (if (first-longer l1 l2) l1 l2)))
    

    使用示例:

    (longest '(5 6 7 8) '(1 2 3)) --> (5 6 7 8)
    (longest '(5 6 7) '(1 2 3 4)) --> (1 2 3 4)
    (longest '(5 6 7) '(1 2 3)) --> (1 2 3)
    

    请注意,应该如何处理等长列表存在歧义:最后一个示例还不如返回 (5 6 7)

    【讨论】:

      【解决方案3】:

      在您的代码中,当没有遇到任何基本情况时,您无需更改任何参数即可调用自己。结果将是默认情况将始终命中所有以下迭代。您似乎认为rest 以某种方式改变了它的论点,但事实并非如此。它在不更改原始参数的情况下返回结果。 (rest '(1 2 3)) ; ==&gt; (2 3)(1 2 3) 仍然是 (1 2 3) 而不是 (2 3)。如果你用(rest x) 更改x(1 2 3),结果仍然是(2 3)x 仍然是(1 2 3)

      这是我的看法:

      ;; return the longest of two lists
      (defun longest (l1 l2)
        (labels ((aux (tl1 tl2)
                   (cond ((null tl2) l1)
                         ((null tl1) l2)                   
                         (t (aux (rest tl1) (rest tl2))))))
          (aux l1 l2)))
      

      如果它们大小相等,您将始终得到第一个参数。这与所有元素具有相同权重的稳定排序相同,您将始终获得相同的第一个值。另一种方法是在它们的长度相等时返回nil,因为没有一个是最长的。

      简单性通常要好得多。以下是我将如何实现它:

      (defun longest (l1 l2)
        (if (< (length l1) 
               (length l2))
            l2
            l1))
      

      【讨论】:

      • +1 在第一个代码上,尽管我不同意第二个代码。 length 不是 O(1)。
      • @WillNess 它们都不是 O(1),但确实第一个是对第二个的优化。 CL 在第二个中等大列表的例子中表现得相当好,所以除非你知道你正在处理非常大的列表或可能的循环列表,否则第二个更短,更容易阅读:) (在 ikarus 方案中,我看到了相同的由于检票口速度更快,优化没有改善length)
      • 检查cdr总是 O(1)。
      • @WillNess 确实如此,但您在两个列表中使用length 时会执行aux (min l1 l2)cdr (+ l1 l2) 次。轻微优化,但没有更好的大 O。
      • O(min(m,n)) 与 O(m+n) 非常不同,即 O(max(m,n))。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-18
      • 1970-01-01
      • 1970-01-01
      • 2014-12-05
      相关资源
      最近更新 更多