【问题标题】:Multiple procedures within a racket conditional?一个球拍内的多个程序有条件?
【发布时间】:2015-11-24 23:12:30
【问题描述】:

我正在研究球拍中的 Project Euler 问题 #3,但不确定如何在条件句中使用多个程序作为一个程序。通常在使用过程语言时,我会使用“while”循环并进行一些变量更新(因此为什么我在下面的代码中使用“set!”(如果有更好的方法——正如我从相关问题中读到的那样) /answers that it's not good practice to mutate variables in racket, then please provide an alternative) and have several processes if a condition is true. 将多条指令作为一个输出语句(通过将其包含在“()”中似乎不是工作,但我相信有一些方法可以做到这一点。

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

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

; 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”区域设为粗体(如果它没有以粗体显示,那么它将被 ** ** 包围(例如:example ). 希望它不会破坏可读性。

谢谢。

【问题讨论】:

    标签: scheme conditional racket


    【解决方案1】:

    @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,则返回一个空列表。
    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))))))
    

    【讨论】:

    • set! 在球拍中不是惯用的,因为它的性能比尾递归/线性迭代更差吗? for 循环的同样问题。顺便感谢您的回答!展示了另一种思考问题的方式。
    • 这不是惯用的,因为 Racket(和 Scheme)人们倾向于以函数式风格编写代码(尽可能少的突变)。当然,一些数据结构,比如向量,可能需要变异才能有效,但是只有有限的情况(比如随机访问)向量是有用的。大多数时候,您可以使用标准列表,而对于这些列表,您可以 100% 不可变/功能正常。
    • 这也适用于for 解决方案吗?我的意思不是功能范式和惯用的球拍。
    • for 实际上是一个推导式(与 C 风格的 for 循环无关),不涉及任何突变。所以它完全是功能性和惯用的。
    【解决方案2】:

    要将多个指令分组到一个语句中,您可以使用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)))
    

    注意最后一条指令的返回值将是(begin ...)的结果。

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

    我想你想做(set! pL (append pL (list divisor)))。现在它不会再返回一个空列表了。

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

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

    【讨论】:

    • 谢谢,现在我可以尝试将递归代码转换为线性迭代。 :) 我唯一不完全理解的是 (list null) 和 '() 之间的确切区别。
    • @Byte (list null) 返回一个元素的列表。 (list) 返回零元素列表。 (list null) 返回与 (list (list)) 相同的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-31
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多