球拍条件内的多个程序?

Multiple procedures within a racket conditional?

我正在研究 Project Euler problem #3 in racket,我不确定如何在条件语句中将多个过程作为一个过程使用。通常在使用过程语言时,我会使用 'while' 循环并进行一些变量更新(因此我在以下代码中使用 'set!' 的原因(如果有更好的方法 - 正如我所读来自相关的 questions/answers,在 racket 中改变变量不是好的做法,那么请提供一个替代方案)并且如果条件为真则有多个过程。将多个指令作为一个输出语句(通过将其包含在'()'中似乎行不通,但我相信有一些方法可以做到这一点。

我考虑过将其分解成小函数,然后制作 2 个较大的函数(1 个用于 'then' 区域,1 个用于“其他区域”),但它似乎不是正确的解决方案(或者至少不是传统的方法)。

如果有帮助,我还包含了此特定代码的错误消息。

; Project Euler # 3
; What is the largest prime factor of the number 600851475143?

(define pL (list null))
(define divisor 2)

(define (pF dividend)
  (if (= dividend 1)
      pL
      (if (= (remainder dividend divisor) 0)
          **(**(append pL (list divisor))
          (set! dividend (/ dividend divisor))
          (set! divisor 2)
          (pF dividend)**)**
          **(**(set! divisor (+ divisor 1))
          (pF dividend)**)**)))

(pF 33)

application: not a procedure;
 expected a procedure that can be applied to arguments
  given: '(() 11)
  arguments.:

我已经把'then'和'else'区域加粗了(如果没有加粗,那么它会被****包围(例如:示例)。希望它不会破坏可读性。

谢谢。

要将多个指令组合在一个语句中,您可以使用 begin

(if (= (remainder dividend divisor) 0)
    (begin (append pL (list divisor)) // Then 
           (set! dividend (/ dividend divisor))
           (set! divisor 2)
           (pF dividend))
    (begin (set! divisor (+ divisor 1)) // Else
           (pF dividend)))

请注意,最后一条指令的return值将是(begin ...)的结果。

但是 运行 具有这些更改的代码将生成 '(())。 这是因为 (append pL (list divisor)) 将 return 一个 new 列表。因此这条指令是无用的,因为你没有捕捉到结果。

我想你想做 (set! pL (append pL (list divisor)))。现在它不再是 return 空列表。

您还希望 pL 变量像这样定义 (define pL '())。现在结果的第一个元素将不再是一个空列表(这是由第一次追加引起的)。

我测试了修改后的代码,它应该可以工作:)

@HyperZ 的回答是正确的。但是,set! 的广泛使用通常不被认为是惯用的 Scheme,因此我想展示一种不同的方式来处理它。我将使用相同的算法,只是在惯用的 Racket 中重新表述:

(define (factors dividend)
  (if (= dividend 1)
      '()
      (let loop ((divisor 2))
        (define-values (q r) (quotient/remainder dividend divisor))
        (if (zero? r)
            (cons divisor (factors q))
            (loop (add1 divisor))))))

这应该很容易阅读:

  1. 如果被除数为 1,return 一个空列表。
  2. 否则,寻找一个合适的除数,从2开始:
    1. 被除数除以除数得到商和余数。
    2. 如果余数为零,结果就是除数加上商的因数。
    3. 否则,除数加1,重试

您实际上可以通过使用 for/first 理解而不是手动内部循环来使代码更具可读性:

(define (factors dividend)
  (if (= dividend 1)
      '()
      (for/first ((divisor (in-naturals 2))
                  #:when (zero? (remainder dividend divisor)))
        (cons divisor (factors (quotient dividend divisor))))))