今天开始学习球拍。我试图找到通过循环追加的正确方法,但找不到答案或自己弄清楚语法。
例如,如果我想要使用 hc-append 的一行九个圆圈,如果不手动键入九个嵌套的 hc-append 程序,我该如何做到这一点?
您需要意识到的第一件事是,Racket 中的“循环”实际上只是递归。在这种情况下,您希望将一堆绘图调用链接在一起。如果我们把它写出来,我们的目标是:
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10)
(hc-append (circle 10))))))))))
我假设我们所有的圆圈都是相同的半径。
现在,由于我们要编写一个递归方法,我们需要考虑我们的基本情况。我们想要画九个圆圈。我们称之为最大圈数max
。我们的基本情况,当我们打破我们的“循环”时,将是当我们到达max
迭代时,或者当(= iterations max)
.
现在是递归本身。我们已经知道我们至少需要传入两个变量,当前迭代iterations
和最大迭代max
。如果您查看上面的代码,您会注意到所有“循环”中的重复元素都是(circle 10)
. 现在有很多方法可以传递它——例如,有些人会选择只传递半径——但我认为最简单的方法是传递圆的图片。
最后,我们还必须传递我们到目前为止所做的图片。也就是说,当我们将一个圆圈附加到我们的链上时,我们需要将它传递回递归方法,以便我们可以继续附加。
现在我们已经解决了这个问题,我们可以定义递归方法的结构,我们称之为circle-chain-recursive
:
(define (circle-chain-recursive iteration max crcle output)
; body here
)
我们方法的“胆量”将是一个if
. 如果我们已经达到最大迭代,则返回输出。否则附加另一个圆圈, increment iteration
,然后再次调用该方法。
(define (circle-chain-recursive iteration max crcle output)
(if (= iteration max)
output
(circle-chain-recursive
(+ 1 iteration) max crcle (hc-append crcle output))))
我个人不喜欢直接调用这样的递归循环方法,所以我会写一个这样的辅助方法:
(define (circle-chain num radius)
(circle-chain-recursive 0 num (circle radius) (circle 0)))
现在,如果我想要一系列 9 个半径为 10 的圆,我所要做的就是调用(circle-chain 9 10)
.
你会注意到我传入(circle 0)
的参数是output
. 这是因为该hc-append
方法需要一个pict
参数。由于我们没有从任何圆圈开始,我将其传递给相当于“空白”或零图片。可能还有其他方法可以传递“空白”图片,但我对slideshow/pict
图书馆不太熟悉,不知道。
我希望这能澄清一点。
Racket 中提供了三种样式的循环:
这种风格使用 Racket Guide 第 11 章“迭代和理解”中描述的语法。它看起来像这样:
(require slideshow/pict)
(for/fold ([result (blank)]) ([i (in-range 9)])
(hc-append (circle 10) result))
在这种风格中,在 for 之后的第一个括号中定义了循环的临时变量。在我的示例中有一个这样的变量,称为result。然后定义迭代变量以及它们迭代的内容。所以在这里,i循环 0 到 8 的数字。循环体为每个i运行一次,每次将结果分配给result,循环的最终值是最后的result值。
这种风格在指南的第 3.8 节中进行了描述。它看起来像这样:
(require slideshow/pict)
(foldl hc-append (blank) (build-list 9 (lambda (i) (circle 10))))
此代码首先列出 9 个圆圈:
(define o (circle 10))
(build-list 9 (lambda (i) o) ---> (list o o o o o o o o o)
列表表示法是更冗长的cons表示法的简写。
(list o o o o o o o o o) ---> (cons o (cons o (cons o (cons o (cons o (cons o (cons o (cons o (cons o empty)))))))))
foldl函数的思考方式是将一段数据转换为一段计算。它将列表作为输入,并将其转换为函数调用的集合。
foldl函数获取列表并用第一个参数替换列表中的每个 cons,并用第二个参数替换列表末尾的空。在示例中,我传递了hc-append函数和(空白),因此替换如下所示:
(foldl hc-append (blank) ...) --->
(hc-append o (hc-append o (hc-append o (hc-append o (hc-append o (hc-append o (hc-append o (hc-append o (hc-append o (blank))))))))))
这一系列函数调用正是您想要避免手动编写的。Foldl为您计算它。
这是罗迪在他的回答中描述的风格。作为编码风格的一般问题,只有在没有简单的 *for、map或fold可能时才应该使用递归风格,例如在遍历递归数据结构时。
冷冻豌豆的罗迪几乎涵盖了这个答案。
我想补充一点,因为这种事情很常见——重复一个过程并积累结果——Racket 语言中有一些功能可以简洁地表达这个想法。其中一项功能是 Racket 的for/fold循环。举个例子:
> (for/fold ([result 'Go!])
([i (in-range 5)])
(list 'Duck result))
'(Duck (Duck (Duck (Duck (Duck Go!)))))
在这里,我们对初始值' Go!积累一个结果。 for/fold的实现是基于 Roddy 描述的递归过程,所以一旦你掌握了基础知识,你就会知道足够舒适地使用for/fold 。
这是一篇旧帖子,但阅读量很大,因此我添加了一个答案。以下使用“named let”的方法也很方便:
(define (f)
(let loop ((counter 1)
(result (blank)))
(if (< counter 10)
(loop (add1 counter)
(hc-append (circle 10) result))
result)))