I don't know much about generators,
however I can propose a solution based on streams (lazily constructed,
possibly infinite lists), which are somewhat similar.
My approach would be to create a stream
whose "state" itself would be a stream of streams.
The individual, inner streams of numbers,
let's call them the 3-streams,
would represent lists of the successive powers of 3, starting with 1,
multiplied by a given power of two.
We can then assemble an infinity of such 3-streams,
one for each successive power of 2, starting with 1.
Let's call this the 2-stream.
The initial state, in ascii-art, is this:
---------------------- --- -- -
| The 2-stream ...
--|----|----|----|---- --- -- -
V V V V
|1| | 2| | 4| | 8|
|3| | 6| |12| |24| ...
|9| |18| |36| |72| The 3-streams
: : : :
Now, we're going to manipulate this so that at any moment,
the 3-streams will be ordered within the 2-stream
with regards to their first elements.
As a consequence the next smallest generated number
will always be the first element of the first 3-stream.
So, to get the next number in the sequence you wish to obtain,
we're going to pull out the first 3-stream,
pull out its first element (which is the number we're interested in),
and then re-insert the 3-stream in the 2-stream
at a position determined by its new first element.
The new state after the first number (1) has been extracted would be:
---------------------- --- -- -
| The 2-stream ...
---|----|----|----|---- --- -- -
V V V V
| 2| | 3| | 4| | 8|
| 6| | 9| |12| |24| ...
|18| |27| |36| |72| The 3-streams
: : : :
Note that this method does not depend on 2^i, 3^j or multiplication specifically
(just on 2^i * 3^j being monotonically increasing with i and j).
I have posted another answer which does, and
is much more simple and fast as a result.
don't trust me: it has nothing to do with the math
Below is an example implementation, using SRFI-41 streams:
(require srfi/41)
; Geometric sequence with initial value 'init', and ratio 'r'
(define (make-geoseq init r)
(stream-cons
init
(make-geoseq (* r init) r)))
; Your power generators
(define pow2 (make-geoseq 1 2))
(define pow3 (make-geoseq 1 3))
; Construct a 3-stream from the pow3 sequence
(define (make-3stream mult)
(stream-map (lambda (x) (* mult x)) pow3))
; Construct the (initial) 2-stream from the pow2 sequence
(define initial-2stream
(stream-map make-3stream pow2))
; Insert a modified 3-stream into the given 2-stream, at the right position
(define (insert two-stream three-stream)
(if (< (stream-car three-stream)
(stream-car (stream-car two-stream)))
; we have the smallest 3-stream, put it at the front
(stream-cons
three-stream
two-stream)
; otherwise, recurse
(stream-cons
(stream-car two-stream)
(insert (stream-cdr two-stream) three-stream))))
; Construct a 2^n * 3^p stream with the given 2-stream as its "state"
(define (make-the-stream current-2stream)
(let*
; pull out the first 3-stream
((first-3s (stream-car current-2stream))
(other-3s (stream-cdr current-2stream))
; use its first element as our next value
(next-val (stream-car first-3s))
; reinsert its tail into the 2-stream's tail
(next-2s (insert other-3s (stream-cdr first-3s))))
; and use the resulting 2-stream to construct the (outer) stream's tail
(stream-cons
next-val
(make-the-stream next-2s))))
; Now, we can construct the stream we want
(define the-stream (make-the-stream initial-2stream))
Using plt-scheme (on my rather crappy hardware):
$ mzscheme -f pow23.scm -e '(display (stream->list (stream-take 20 the-stream)))'
(1 2 3 4 6 8 9 12 16 18 24 27 32 36 48 54 64 72 81 96)
$ time mzscheme -f pow23.scm -e '(display (stream-ref the-stream 10000))'
161968247347450370721577384417107686788864605658546176
real 0m12.550s
user 0m11.005s
sys 0m0.340s
Implementing this with generators could be done I guess,
but the tricky part would be implementing (insert)
.
You could do so by composing generators,
but you would end up adding one "layer" every time a number is pulled,
whereas a stream created with (insert)
shares its tail with the original one
(the "layers" eventually merge).