【问题标题】:Do two things in Racket "for loop"在球拍“for循环”中做两件事
【发布时间】:2016-10-11 11:43:26
【问题描述】:

我在球拍中运行一个 for 循环,对于列表中的每个对象,我想执行两件事:如果项目满足条件,(1)将其附加到我的新列表中,(2)然后打印列表。但我不确定如何在 Racket 中执行此操作。

这是我的除数函数:在 if 语句中,我检查范围内的数字是否可以整除 N。如果可以,我将该项添加到我的新列表 L。完成所有循环后,我打印 L。但是由于某些未知原因,该函数仍将 L 作为空列表返回,所以我想看看 for 循环在每个循环中做了什么。但显然球拍似乎并没有在一个“for循环”中采取两个动作。那么我该怎么做呢?

    (define (divisor N)
       (define L '())
      (for ([i (in-range 1 N)])
         (if (equal? (modulo N i) 0)
             (append L (list i))
             L)
           )
       write L)

提前非常感谢!

【问题讨论】:

  • 我认为您在这里得到了 4 个很好的答案,还有什么不清楚的地方吗?如果是这样,请告诉我们缺少什么。否则,请将您喜欢的答案标记为“已接受”。

标签: for-loop racket


【解决方案1】:

注意:此答案建立在@uselpa 的答案之上,我对此表示赞同。

for 表单有一个可选的#:when 子句。使用for/fold

#lang racket

(define (divisors N)
  (reverse (for/fold ([xs '()])
                     ([n (in-range 1 N)]
                      #:when (zero? (modulo N n)))
             (displayln n)
             (cons n xs))))

(require rackunit)
(check-equal? (divisors 100)
              '(1 2 4 5 10 20 25 50))

我知道您的核心问题是关于如何显示每个中间列表。但是,如果您不需要这样做,使用for/list 会更简单:

 (define (divisors N)
  (for/list ([n (in-range 1 N)]
             #:when (zero? (modulo N n)))
    n))

换句话说,传统的方案(filter __ (map __))filter-map 也可以使用#:when 子句在Racket 中表示为for/list


有很多方法可以表达这一点。我认为我们所有答案的共同点是您可能希望避免使用forset! 来构建结果列表。这样做不是惯用的 Scheme 或 Racket。

【讨论】:

    【解决方案2】:

    @sepp2k 已回答您的问题,为什么您的结果总是null

    在 Racket 中,更惯用的方法是使用 for/fold

    (define (divisor N)
      (for/fold ((res null)) ((i (in-range 1 N)))
        (if (zero? (modulo N i))
            (let ((newres (append res (list i))))
              (displayln newres)
              newres)
            res)))
    

    测试:

    > (divisor 100)
    (1)
    (1 2)
    (1 2 4)
    (1 2 4 5)
    (1 2 4 5 10)
    (1 2 4 5 10 20)
    (1 2 4 5 10 20 25)
    (1 2 4 5 10 20 25 50)
    '(1 2 4 5 10 20 25 50)
    

    由于append 的性能并不高,你通常使用cons 来代替,最后得到一个reverse 需要的列表:

    (define (divisor N)
      (reverse
       (for/fold ((res null)) ((i (in-range 1 N)))
         (if (zero? (modulo N i))
             (let ((newres (cons i res)))
               (displayln newres)
               newres)
             res))))
    
    > (divisor 100)
    (1)
    (2 1)
    (4 2 1)
    (5 4 2 1)
    (10 5 4 2 1)
    (20 10 5 4 2 1)
    (25 20 10 5 4 2 1)
    (50 25 20 10 5 4 2 1)
    '(1 2 4 5 10 20 25 50)
    

    【讨论】:

    • 这里的折叠比filter 更可取吗?
    • 对于filter,您必须首先创建(range 1 N) 的完整列表;这个解决方案没有。你将如何插入你的displaylnfilter?。无论如何,由于问题是关于 for 循环,我尝试说明在这种情况下使用哪一个。
    【解决方案3】:

    可以按照你的意愿创建列表,只要你set!append 返回的值(记住:append就地修改列表,它创建一个必须存储在某处的新列表)并在最后调用 write

    (define (divisor N)
      (define L '())
      (for ([i (in-range 1 N)])        ; generate a stream in the range 1..N
        (when (zero? (modulo N i))     ; use `when` if there's no `else` part, and `zero?`
                                       ; instead of `(equal? x 0)`
          (set! L (append L (list i))) ; `set!` for updating the result
          (write L)                    ; call `write` like this
          (newline))))                 ; insert new line
    

    …但这不是在 Scheme 中做事的惯用方式,尤其是在 Racket 中:

    • 我们尽可能避免像set!这样的变异操作
    • 在循环中write 是个坏主意,您会在控制台中打印出大量文本
    • 不建议将append 元素放在列表末尾,这将花费二次时间。我们更喜欢使用cons 在列表头部添加新元素,如果需要,在末尾反转列表

    考虑到上述所有因素,这就是我们如何在 Racket 中实现更惯用的解决方案 - 使用 stream-filter,不打印,也不使用 for 循环:

    (define (divisor N)
      (stream->list                       ; convert stream into a list
       (stream-filter                     ; filter values
        (lambda (i) (zero? (modulo N i))) ; that meet a given condition
        (in-range 1 N))))                 ; generate a stream in the range 1..N
    

    另一个选项(类似于@uselpa 的答案),这次使用for/fold 进行迭代和累积值 - 再次,无需打印:

    (define (divisor N)
      (reverse                    ; reverse the result at the end
       (for/fold ([acc '()])      ; `for/fold` to traverse input and build output in `acc`
         ([i (in-range 1 N)])     ; a stream in the range 1..N
         (if (zero? (modulo N i)) ; if the condition holds
             (cons i acc)         ; add element at the head of accumulator
             acc))))              ; otherwise leave accumulator alone
    

    无论如何,如果需要打印中间的所有步骤,这是一种方法 - 但效率低于以前的版本:

    (define (divisor N)
      (reverse
       (for/fold ([acc '()])
         ([i (in-range 1 N)])
         (if (zero? (modulo N i))
             (let ([ans (cons i acc)])
               ; inefficient, but prints results in ascending order
               ; also, `(displayln x)` is shorter than `(write x) (newline)`
               (displayln (reverse ans))
               ans)
             acc))))
    

    【讨论】:

    • @uselpa 现在显示它们。我是在你发布它的同时写的:(
    • @uselpa 谢谢!我相信这个答案现在已经完全不同了:)
    • 不幸的是,优雅并不总能得到回报。当 N=1000000 时,基于流的解决方案在我的计算机上比基于折叠的解决方案慢 17.5 倍。
    【解决方案4】:

    要在 Racket 的 for 循环中执行多项操作,只需将它们逐个编写即可。因此,要在每次迭代后显示 L,您可以这样做:

    (define (divisor N)
      (define L '())
      (for ([i (in-range 1 N)])
        (if (equal? (modulo N i) 0)
            (append L (list i))
            L)
        (write L))
      L)
    

    请注意,调用函数需要括号,所以它是(write L) - 而不是write L。我还用 L 在 for 循环之外替换了你的 write L ,因为你(大概)想在最后从函数返回 L - 不要打印它(因为你周围没有括号,反正它就是这么做的)。

    这将告诉您L 的值始终是()。原因是你永远不会改变Lappend 所做的是返回一个新列表 - 它不会影响其任何参数的值。所以使用它而不使用它的返回值并没有做任何有用的事情。

    如果你想让你的for循环工作,你需要使用set!来实际改变L的值。然而,避免突变而是使用filter 或递归来解决这个问题会更加惯用。

    【讨论】:

      【解决方案5】:

      'Named let',一个通用的方法,可以在这里使用:

      (define (divisors N)
        (let loop ((n 1)                ; start with 1
                   (ol '()))            ; initial outlist is empty;
          (if (< n N)
              (if(= 0 (modulo N n))
                 (loop (add1 n) (cons n ol))  ; add n to list
                 (loop (add1 n) ol)           ; next loop without adding n
                 )
              (reverse ol))))           
      

      在输出之前反转,因为项目已添加到列表的头部(带有缺点)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-07
        • 1970-01-01
        • 2011-04-12
        • 2015-01-06
        • 1970-01-01
        • 1970-01-01
        • 2023-04-06
        • 1970-01-01
        相关资源
        最近更新 更多