3

今天开始学习球拍。我试图找到通过循环追加的正确方法,但找不到答案或自己弄清楚语法。

例如,如果我想要使用 hc-append 的一行九个圆圈,如果不手动键入九个嵌套的 hc-append 程序,我该如何做到这一点?

4

4 回答 4

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. 如果我们已经达到最大迭代,则返回输出。否则附加另一个圆圈, 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图书馆不太熟悉,不知道。

我希望这能澄清一点。

于 2012-07-26T00:23:53.740 回答
4

Racket 中提供了三种样式的循环:

#1 for 循环风格

这种风格使用 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值。

#2 地图和折叠样式。

这种风格在指南的第 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为您计算它。

#3 递归风格

这是罗迪在他的回答中描述的风格。作为编码风格的一般问题,只有在没有简单的 *for、mapfold可能时才应该使用递归风格,例如在遍历递归数据结构时。

于 2012-07-26T17:37:35.310 回答
3

冷冻豌豆的罗迪几乎涵盖了这个答案。

我想补充一点,因为这种事情很常见——重复一个过程并积累结果——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 。

于 2012-07-26T17:38:25.153 回答
0

这是一篇旧帖子,但阅读量很大,因此我添加了一个答案。以下使用“named let”的方法也很方便:

(define (f)
  (let loop ((counter 1)
             (result (blank)))
    (if (< counter 10)
        (loop (add1 counter)
              (hc-append (circle 10) result))
        result)))
于 2016-10-11T03:23:39.377 回答