我必须在Scheme中定义一个可变函数,它采用以下形式:(define (n-loop procedure [a list of pairs (x,y)])
,其中对的列表可以是任何长度。
每对指定一个下限和上限。也就是说,以下函数调用:(n-loop (lambda (x y) (inspect (list x y))) (0 2) (0 3))
产生:
(list x y) is (0 0)
(list x y) is (0 1)
(list x y) is (0 2)
(list x y) is (1 0)
(list x y) is (1 1)
(list x y) is (1 2)
显然,car和cdr将不得不参与到我的解决方案中。但让这件事变得困难的规定如下。根本不需要使用赋值语句或迭代循环(while和for)。
我可以使用while和for在对列表中进行索引来处理它,但似乎我必须使用递归。我不想要任何代码解决方案,除非你觉得有必要解释,但有人建议如何攻击它吗?
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中循环的惯用编写方式,但如果您的赋值不允许使用命名的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
循环转换为一个基于命名let
的循环。(或者,您可能需要更进一步,将外环和内环都转换为letrec
形式。)