【问题标题】:How do you run equivalent of nested for loops in scheme您如何在方案中运行等效的嵌套 for 循环
【发布时间】:2021-01-09 01:56:11
【问题描述】:

我正在研究如何将此伪代码转换为惯用方案:

for c in range(1, 1000):
    for b in range(1, c):
        for a in range(1, b):
            do something if condition is true

【问题讨论】:

  • 翻译无意义的语法会导致代码单一。
  • 这有点令人遗憾(但可能是正确的),因为它提供了一个很好的例子来展示宏可以做什么。
  • 参见例如this。没有宏,它还提到了“命名的 let”,例如喜欢pastebin.com/U8ai7hLs
  • @WillNess:我也有(抱歉,尽管按钮很明显,但我不知道你可以)。我认为这是值得的,因为有一些有趣的答案可以展示 Lisp 家族语言在语言上的强大程度,尽管这可能只是因为我有一个宏可以做到这一点......
  • @tfb 在这里,我粘贴了一个我难以在方案中优雅表达的问题(我在方案和 python 中的解决方案):pastebin.com/67GT8ep2

标签: loops scheme nested-loops idioms code-translation


【解决方案1】:

您的伪代码被直接转换为 Scheme,作为一个名为 lets 的嵌套序列,如下所示

(let loopc ((c 1))                ; start the top loop with c = 1,
  (if (> c 1000)                  ; if `c > 1000`,
    #f                            ;   exit the top loop ; or else,
    (let loopb ((b 1))            ; start the first-nested loop with b = 1,
      (if (> b c)                 ; if `b > c`,
        (loopc (+ c 1))           ;   end the first-nested loop, and
                                  ;   continue the top loop with c := c+1 ; or else,
        (let loopa ((a 1))        ; start the second-nested loop with a = 1,
          (if (> a b)             ; if `a > b`,
            (loopb (+ b 1))       ;   end the second-nested loop, and
                                  ;   continue the first-nested loop with b := b+1
            (begin                ; or else,
              (if condition       ;   if condition holds,
                (do_some a b c)   ;     do something with a, b, c,
                #f)               ;   and then,
              (loopa (+ a 1))     ;   continue the second-nested loop with a := a+1
              )))))))

当然,这有点复杂且容易出错。另一方面,通过在loopa 中调用loopc(总是在tail位置,注意!),如果需要的话。

例如,上面的代码直接适用于你在cmets中陈述的问题,找到一个和为1000的毕达哥拉斯三元组。此外,当你找到解决方案时,你可以直接调用(loopc 1001)立即退出整个三重嵌套循环构造。


顺便说一句,

for c in range(3, 1000):
    for b in range(2, c):
        for a in range(1, b):
            if a**2 + b**2 == c**2 and a + b + c == 1000:
                print(a * b * c)

不是最有效的解决方案。至少,首先,

for c in range(3, 1000):
    for b in range(2, 1000-c-1):
        for a in range(1, 1000-c-b):
            if a**2 + b**2 == c**2 and a + b + c == 1000:
                print(a * b * c)

此外,

for c in range(3, 1000):
    c2 = c**2
    for b in range(2, 1000-c-1):
        a = 1000-c-b
        if a**2 + b**2 == c2:
                print(a * b * c)
                # exit the loops

【讨论】:

    【解决方案2】:

    在 Scheme 中表达任何类型的循环(以及更通用的控制结构)都非常容易。例如,如果你想要一个简单计数的循环,你可以这样做:

    (let loop ([i 0])
      (if (>= i 10)
          (values)
          (begin
            (display i)
            (loop (+ i 1)))))
    

    你显然可以嵌套这些。

    但这比你在 Python 中编写的内容更容易阅读:

    for i in range(10):
        print(i)
    

    嗯,好的。但是 Lisp 家族的语言是关于构建语言的:如果你想要的语法不存在,你让它存在。这是一个这样做的例子:

    (define-syntax nloop*
      ;; Nested numerical loop
      (syntax-rules ()
        [(_ () form ...)
         (begin form ...
                (values))]
        [(_ ((variable lower-inclusive upper-exclusive) more ...) form ...)
         (let loop ([variable lower-inclusive])
           (if (< variable upper-exclusive)
               (begin
                 (nloop* (more ...) form ...)
                 (loop (+ variable 1)))
               (values)))]
        [(_ ((variable start-inclusive end-exclusive step) more ...) form ...)
         (let ([cmp? (if (>= step 0) < >)])
           (let loop ([variable start-inclusive])
             (if (cmp? variable end-exclusive)
                 (begin
                   (nloop* (more ...) form ...)
                   (loop (+ variable step)))
                 (values))))]))
    

    现在:

    > (nloop* ((i 0 10))
        (print i))
    0123456789
    

    还有

    > (nloop* ((i 0 10)
               (j 20 0 -4))
        (displayln (list i j)))
    (0 20)
    (0 16)
    (0 12)
    (0 8)
    (0 4)
    ...
    (9 20)
    (9 16)
    (9 12)
    (9 8)
    (9 4)
    

    所以我刚刚发明的这个nloop* 结构可以完美地进行一般数值循环,包括嵌套循环(这就是为什么*:nloop(不存在)会并行循环)。

    当然,在像 Racket 这样的工业强度方案派生语言中,已经有这样的构造:

    (for ([i (in-range 10)])
      (for ([j (in-range 20 0 -4)])
        (displayln (list i j))))
    

    这将是惯用的 Racket 方式。但是for 及其所有变体和基础设施只是语言结构,原则上您可以自己用非常简单的方案编写。

    这些是专为构建语言而设计的语言。

    【讨论】:

      【解决方案3】:

      有很多令人难以置信的方法可以解决您的问题。这是我现在想到的最简单的:

      (define iota
        (lambda (n k)
          ((lambda (s) (s s 1))
           (lambda (s x)
             (k x)
             (or (= n x)
                 (s s (+ x 1)))))))
      
      (iota 1000
            (lambda (c)
              (iota c
                    (lambda (b)
                      (iota b
                            (lambda (a)
                              (newline)
                              (display a) (display " ")
                              (display b) (display " ")
                              (display c) (display " ")))))))
      

      如果c的初始区间是1..3而不是1..1000,则嵌套代码将包含(a b c)的这些值:

      a b c
      1 1 1
      1 1 2
      1 2 2
      2 2 2
      1 1 3
      1 2 3
      2 2 3
      1 3 3
      2 3 3
      3 3 3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多