在 Scheme 中进行循环的标准方法是使用尾递归。事实上,假设你有这个循环:
(do ((a 0 b)
(b 1 (+ a b))
(i 0 (+ i 1)))
((>= i 10) a)
(eprintf "(fib ~a) = ~a~%" i a))
这实际上被宏扩展为如下内容:
(let loop ((a 0)
(b 1)
(i 0))
(cond ((>= i 10) a)
(else (eprintf "(fib ~a) = ~a~%" i a)
(loop b (+ a b) (+ i 1)))))
进一步,它被宏扩展为这个(我不会宏扩展cond
,因为这与我的观点无关):
(letrec ((loop (lambda (a b i)
(cond ((>= i 10) a)
(else (eprintf "(fib ~a) = ~a~%" i a)
(loop b (+ a b) (+ i 1)))))))
(loop 0 1 0))
你应该看到letrec
这里并想,“啊哈!我看到了递归!”。确实可以(特别是在这种情况下,尾递归,尽管letrec
也可以用于非尾递归)。
Scheme 中的任何迭代循环都可以这样重写(命名let
版本是在 Scheme 中惯用地编写循环的方式,但如果您的分配不允许您使用 named let
,请进一步扩展并使用letrec
)。我上面描述的宏扩展是简单而机械的,您应该能够看到一个如何转换为另一个。
由于您的问题询问可变参数函数如何,您可以这样编写可变参数函数:
(define (sum x . xs)
(if (null? xs) x
(apply sum (+ x (car xs)) (cdr xs))))
(顺便说一句,这是一种非常低效的编写sum
函数的方法;我只是用它来演示如何发送(使用apply
)和接收(使用不正确的 lambda 列表)任意数量的参数。)
更新
好的,这里有一些一般性建议:您将需要两个循环:
- 一个外循环,它通过范围级别(那是你的可变参数)
- 一个内部循环,循环遍历每个范围级别中的数字
在每个循环中,仔细考虑:
- 起始条件是什么
- 结束条件是什么
- 你想在每次迭代中做什么
- 在迭代之间是否需要保持任何状态
特别是,请仔细考虑最后一点,因为这是在给定任意数量的嵌套级别的情况下嵌套循环的方式。(在我下面的示例解决方案中,这就是cur
变量。)
在你决定了所有这些事情之后,你就可以构建你的解决方案的一般结构。我将在下面发布我的解决方案的基本结构,但是在查看我的代码之前,您应该好好考虑一下要如何解决问题,因为它可以让您很好地掌握两者之间的差异您的解决方案方法和我的方法,它将帮助您更好地理解我的代码。
此外,不要害怕首先使用命令式循环(如 )来编写它,然后在一切正常时do
将其转换为等效的命名。let
只需重新阅读第一部分以了解如何进行转换。
说了这么多,这是我的解决方案(去掉了细节):
(define (n-loop proc . ranges)
(let outer ((cur ???)
(ranges ranges))
(cond ((null? ranges) ???)
(else (do ((i (caar ranges) (+ i 1)))
((>= i (cadar ranges)))
(outer ??? ???))))))
请记住,一旦你得到这个工作,你仍然需要将do
循环转换为一个基于 named的循环let
。(或者,您可能需要更进一步,将外循环和内循环都转换为它们的letrec
形式。)