今天开始学习球拍。我试图找到通过循环追加的正确方法,但无法找到答案或自己弄清楚语法。
例如,如果我想使用hc-append排九个圆圈,如何在不手动输入九个嵌套的hc-append程序的情况下执行此操作?
答案 0 :(得分:8)
你需要意识到的第一件事是在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
。如果我们已达到最大迭代次数,则返回输出。否则添加另一个圆圈,递增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)))
现在,如果我想要一系列10个半径为10的圆圈,我只需要拨打(circle-chain 9 10)
。
您会注意到我传递了(circle 0)
作为名为output
的参数。这是因为hc-append
方法需要pict
参数。由于我们没有开始使用任何圆圈,因此我将其传递给相当于“空白”或零的图像。可能还有其他方式传递“空白”图片,但我不太熟悉slideshow/pict
图书馆来了解它。
我希望稍微澄清一下。
答案 1 :(得分:4)
Racket中有三种循环样式:
此样式使用“球拍指南”第11章中描述的语法"Iterations and Comprehensions"。它看起来像这样:
(require slideshow/pict)
(for/fold ([result (blank)]) ([i (in-range 9)])
(hc-append (circle 10) result))
在这种风格中,在 for 之后的第一个括号中定义了循环的临时变量。在我的示例中有一个这样的变量,称为 result 。然后定义迭代变量以及它们迭代的内容。所以在这里, i 循环遍历0到8的数字。循环体为每个 i 运行一次,每次将结果分配给 result < / em>,循环的最终值是最后 result 的值。
Section 3.8 of the Guide中描述了这种风格。它看起来像这样:
(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)
列表表示法是更详细的缺点表示法的简写。
(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 ,并替换结尾处的 empty 。带有第二个参数的列表。在示例中,我传递了 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 为您计算。
这是Roddy在他的回答中描述的风格。作为编码风格的一般问题,如果没有简单的* for, map 或 fold ,则应该只使用递归样式,例如遍历递归数据时结构
答案 2 :(得分:3)
冷冻豌豆的罗迪有这个答案。
我想补充一点,因为这类事情是如此常见 - 重复一个过程并积累结果 - 在Racket语言中有一些功能可以简洁地表达这个想法。其中一个功能是Racket的for/fold循环。举个例子:
> (for/fold ([result 'Go!])
([i (in-range 5)])
(list 'Duck result))
'(Duck (Duck (Duck (Duck (Duck Go!)))))
在这里,我们在初始值'Go!上重复操作(列出'Duck结果)以累积结果。 for / fold 的实现是基于Roddy描述的递归过程,所以一旦你掌握了基础知识,你就会知道足够轻松地使用 for / fold 。
答案 3 :(得分:0)
这是一篇很老的帖子,但它被高度阅读,因此我正在添加一个答案。使用'named let'的方法也非常方便:
(define (f)
(let loop ((counter 1)
(result (blank)))
(if (< counter 10)
(loop (add1 counter)
(hc-append (circle 10) result))
result)))