-1

I am looking on how to translate this pseudocode to idiomatic scheme:

for c in range(1, 1000):
    for b in range(1, c):
        for a in range(1, b):
            do something if condition is true
Will Ness
  • 70,110
  • 9
  • 98
  • 181
alwalo
  • 11
  • 3
  • 1
    Translating meaningless syntax leads to unidiomatic code. – molbdnilo Sep 23 '20 at 00:35
  • It's kind of sad (but probably correct) this got closed because it offers a great example to show what macros can do. –  Sep 23 '20 at 22:18
  • see e.g. [this](http://community.schemewiki.org/?syntax-dotimes). without the macros, it also mentions the "named let", e.g. like https://pastebin.com/U8ai7hLs – Will Ness Sep 24 '20 at 15:51
  • @tfb I voted to reopen. – Will Ness Sep 24 '20 at 17:44
  • 1
    @WillNess: I just have too (sorry I didn't know you could despite the obvious button). I think it's worth it because there are potential interesting answers which show how linguistically powerful Lisp-family languages are, although this may only be because I have a macro which does this... –  Sep 24 '20 at 17:58
  • it's simple enough without any macros too, just some nested named lets too can do it. – Will Ness Sep 24 '20 at 18:08
  • @WillNess: of course. But a macro can make the syntax go away. –  Sep 24 '20 at 23:40
  • 1
    @tfb Here I paste a problem that I struggle to elegantly express in scheme (with my solutions in scheme and python): https://pastebin.com/67GT8ep2 – alwalo Sep 25 '20 at 07:56

3 Answers3

1

It is pretty easy to express any kind of loop (and indeed much more general control structures) in Scheme. For instance if you want a loop which simply counts you can do it like this:

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

And you can obviously nest these.

But this is a lot less easy to read than, for instance, what you would write in Python:

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

Well, OK. But Lisp-family languages are about building languages: if the syntax you want does not exist, you make it exist. Here is an example of doing that:

(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))))]))

And now:

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

But also

> (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)

So this nloop* construct I have just invented will do perfectly general numerical loops, including nested ones (this is why the *: nloop (which does not exist) would loop in parallel).

Of course in industrial-strength scheme-derived languages like Racket there are already constructs for doing this:

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

would be the idiomatic Racket way of doing this. But for and all its variants and infrastructure are just language constructs that you could in principle write yourself in a very bare-bones Scheme.

These are languages which are designed for building languages.

1

Your pseudocode is translated into Scheme straightforwardly enough as a sequence of nested named lets, as

(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
              )))))))

Of course it's a bit involved and error prone. On the other hand it is easy to shortcut the innermost loop (on as) and restart the top one (on cs) directly, by calling loopc from within loopa (always in tail position, NB !), should the need arise.

For example, the above code is directly applicable to the problem which you state in the comments, of finding a Pythagorean triplet which sums to 1000. Moreover, when you've hit upon the solution, you can directly call (loopc 1001) to immediately exit the whole threefold nested loops construction.


As a side note,

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)

is not the most efficient solution. That would be, at least, first,

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)

and furthermore,

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
Will Ness
  • 70,110
  • 9
  • 98
  • 181
0

There are incredible many ways to cope with your problem. Here is the simplest that passed in my mind now:

(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 " ")))))))

If the initial interval of c is 1..3 instead of 1..1000, the nested code will contain these values of (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
alinsoar
  • 15,386
  • 4
  • 57
  • 74