Article

More on Racket Generators

I've been playing around, as I said, with Euler 9, which presented me with the question of how to generate Pythagorean triples—this is the interesting part of the problem (it's trivial, once you can do this, to solve it).

I noted in my last post on the subject that Racket has a syntax for creating generator and I used it to produce a generator off of a stream:

(define evens (stream-cons 2 (stream-map add2 evens)))
(define gevens (generator () (for ([e evens]) (yield e))))

Naturally this sort of pattern has been thought about before, and we have the following (easier) syntactic form:

(define gevens (sequence->generator evens))

Originally this suggested to me that the best way to try to solve the problem was to generate the triples one by one and then evaluate their sum. It turns out that this would require something like a nested generator, and I'm not quite that sophisticated yet.

There are several methods for generating Pythagorean triples. The naive solution here is an n-cubed algorithm. It can be optimized readily with a variety of tricks, but I decided to use Dickson's method since it seemed most amenable to creating a generator. (Ternary trees are probably rather faster, and my own code does this a bit naively anyway. Be that as it may, premature optimization and all that.)

The rub in Dickson's method is that it requires factoring, which is a relatively slow process. But assuming you have a factors function written, it's fairly straightforward to write a method to create (lists of) Pythagorean triples:

(define (dickson r)
  (letrec
    ([rs (/ (sq r) 2)]
     [calc-triple (lambda (fa)
                    (let ([s (car fa)]
                          [t (cadr fa)])
                      (list (+ r s) (+ r t) (+ r s t))))])
    (map calc-triple (factors rs))))

Because the solution to Euler 9 is a relatively small number, it's quick to compute: an average of 8.5ms on 1000 iterations on my laptop. This is substantially faster than the naive solution (the linked StackOverflow article suggests a 30s response, though hardware has changed a lot in the interim I'd bet).

Unless your factorization scales well, this is a relatively slow method to generate triples efficiently—but this is a subject for another time.