【问题标题】:Getting tail call optimization in mutual recursion in Scheme在Scheme中进行相互递归的尾调用优化
【发布时间】:2021-01-02 05:36:57
【问题描述】:

在为 MIT/GNU Scheme (rel 9.2) 中的 oddeven 函数开发经典练习代码时,我遇到了一个问题,我的代码不会因大整数值而终止。 首先,我测试了以下代码,它同时处理正值和负值:

(define error-message-number "Error. x must be a number")

(define odd?
  (lambda (x)
    (cond 
      ((not (integer? x)) error-message-number)
      ((= x 0) #f)
      ((< x 0) (even? (+ x 1))) ;for negatives
      (else (even? (- x 1)))))) ;if number n is odd then n - 1 is even 

(define even?
  (lambda (x)
    (cond 
      ((not (integer? x)) error-message-number)
      ((= x 0) #t)
      ((< x 0) (odd? (+ x 1))) ;for negatives
      (else (odd? (- x 1)))))) ;if number n is even then n - 1 is odd

(assert (equal? #f (even? 100000000001)))(assert (equal? #t (odd? -100000000001))) 的调用不会在我的机器上终止,而例如(assert (equal? #f (even? 1001)))(assert (equal? #t (odd? -1001))) 做。 我的第一个想法是代码没有针对正确的尾递归进行优化,而我在每个函数中没有一个,而是两个尾调用。 所以,我决定简化任务,只考虑正整数,如下所示:

(define error-message-positive "Error. x must be a nonnegative number")

(define odd-positive?
  (lambda (x)
    (cond 
      ((not (integer? x)) error-message-number)
      ((= x 0) #f)
      ((< x 0) error-message-positive) ;for negatives
      (else (even? (- x 1)))))) ;if number n is odd then n - 1 is even 

(define even-positive?
  (lambda (x)
    (cond 
      ((not (integer? x)) error-message-number)
      ((= x 0) #t)
      ((< x 0) error-message-positive) ;for negatives
      (else (odd? (- x 1)))))) ;if number n is even then n - 1 is odd

但是这个版本也不返回大整数。 所以,我有这些相关的问题

  1. 功能。 MIT/GNU 方案中的相互递归函数是否经过优化?
  2. 诊断。 有什么方法可以判断出这些函数确实被 Scheme 编译器/解释器针对相互尾递归进行了优化。那么,如何判断问题出在(不存在)尾递归优化或其他问题上。
  3. 什么是正确的相互尾递归? 我的初始代码是否符合优化条件?我的第二次参加是否符合资格?

【问题讨论】:

  • MIT 确实可以让你跟踪函数调用。

标签: recursion scheme tail-recursion mit-scheme mutual-recursion


【解决方案1】:
  1. 什么是正确的相互尾递归?

就像您的代码一样,它们的两个版本。

  1. 诊断。

Empirical orders of growthFTW!

您的诊断可能不正确。特别是,在我的机器上,在 Racket 中,您的代码完成的预期时间是 40 分钟。而且它似乎确实在整体内存中运行。

是的,即使在恒定空间中运行,它仍然需要与参数大小成线性关系的时间。我怎么知道?我只是在时钟墙上时间测量它,它确实是线性的,即 n1 幂律。大约。 (即无论测量为 1.02 还是 0.97,它仍然表示线性增长率。大约。这才是最重要的)。

另见:

  1. 功能。 MIT/GNU Scheme 中的相互递归函数是否进行了优化?

必须如此,因为尾调用优化在语言规范中。并且 TCO 不仅仅是尾递归,据我了解,即使下一步调用的决定是动态的(更不用说静态的了,在代码中很明显,因为它在您的情况下)它仍然必须在一个尾调用时在恒定堆栈空间中运行最终导致再次进入相同的功能。尾调用就是尾调用,无论调用什么,都必须优化。我目前没有准备好官方报价。

【讨论】:

  • 威尔,非常感谢您提供的答案和链接。我知道就时间 O(n) 而言,这是一个缓慢的算法。如果可能的话,我将研究这些链接并尝试进行一些优化,以减少运行时间。附言我使用了一台相当旧的慢速笔记本电脑,所以这可能是代码“永不终止”的原因。
  • 不客气。 :) 或者您可能没有尝试等待 40 分钟? :) 顺便说一句,您可以运行它只需要几秒钟的较小尺寸,然后运行两倍尺寸,然后计算预计时间。关于让它更快:你基本上是在向零计数。你可以试着用两个数来数。或四人组。或八分之一。或 16 秒。看?您可以逐渐增加减法量加倍,检查您是否仍高于 0。因此总时间将是对数。当然,我们总是可以除以 2 并检查余数,但这有什么好玩的呢? :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-30
  • 2014-07-24
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多