如何以非平凡的方式组合两个发电机

时间:2010-01-05 21:09:48

标签: scheme puzzle generator

我有一个生成所有正整数的生成器,它是2的幂,另一个生成所有3的幂的整数。我现在需要用它们来生成2 ^ i * 3 ^ j形式的整数i,j> = 0,0按升序排列。

我认为使用生成器的目的是减少内存消耗。我一直试图这样做一段时间但无济于事。请帮忙。

7 个答案:

答案 0 :(得分:6)

使用自读流

您可以使用自读流来解决此问题:

   -----------        -----------
   |  pow 2  |------->|         |
   -----------        |         |
                      |  merge  |-------+------------>
   -----------        |         |       |
.->|   x 3   |------->|         |       |
|  -----------        -----------       |
\_______________________________________/

第一个流产生两个幂, 而第二个确保所有生成的数字 乘以3并重新注入输出。 合并运算符确保输出已排序。

请注意,我们必须使用1“播种”输出流, 或者第一个元素在评估时会尝试自我产生。

以下是代码:

(require srfi/41)

(define (merge s1 s2)
  (stream-match s1 ((x . xs)
    (stream-match s2 ((y . ys)
      (if (< x y)
        (stream-cons x (merge xs s2))
        (stream-cons y (merge ys s1))))))))

(define (the-stream)
  (letrec ((s
    (stream-cons 1 (merge (stream-map     (lambda (x) (* 3 x)) s)
                          (stream-iterate (lambda (x) (* 2 x)) 2)))))
  s))

与我的其他提案相比,它非常简单快捷, 因为除了单调性之外它还使用问题的算术属性。 我错了,它也可以推广(即将推出)

$ mzscheme -f feedback.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 feedback.scm -e '(display (stream-ref (the-stream) 10000))'
161968247347450370721577384417107686788864605658546176
real    0m1.746s
user    0m1.344s
sys     0m0.156s

使用生成器和队列

我们也可以用python的生成器实现这个, 但我们需要使用队列来存储在反馈循环中等待的数字:

# Merge the output of two generators
def merge(g1, g2):
    v1 = g1.next()
    v2 = g2.next()
    while 1:
        if v1 < v2:
            yield v1
            v1 = g1.next()
        else:
            yield v2
            v2 = g2.next()

# Generates the powers of 2, starting with n
def pow2(n):
    while 1: yield n; n *= 2

# Generates values shifted from the given 'q' and multiplied by 3
def mul3(q):
    while 1: yield q.pop(0) * 3

# The generator we want
def pow23():
    q = []
    v = 1
    g = merge(pow2(2), mul3(q))
    while 1:
        yield v
        q.append(v)
        v = g.next()

g23 = pow23()
for i in range(10000): g23.next()
print g23.next()

这有点不太优雅(恕我直言), 但是发电机更轻巧:

$ time python feedback.py 
161968247347450370721577384417107686788864605658546176
real    0m0.150s
user    0m0.112s
sys     0m0.012s

值得一提的是,我已经完成了一个方案实施 (使用闭包作为发电机) 表现出大致相同的表现。

答案 1 :(得分:3)

我对发电机知之甚少, 但我可以提出一个基于 streams 的解决方案(懒惰构建, 可能是无限的列表),有些相似。

我的方法是创建一个流 其“状态”本身就是一股溪流。

个人,内在的数字流, 让我们称他们为3流, 将代表3的连续幂的列表,从1开始, 乘以给定的2的幂。 然后我们可以组装这样的3流的无限, 每个连续2次幂的一个,从1开始。 我们称之为2流。

ascii-art中的初始状态是:

---------------------- --- -- -
| The 2-stream ...
--|----|----|----|---- --- -- -
  V    V    V    V
 |1| | 2| | 4| | 8|
 |3| | 6| |12| |24| ...
 |9| |18| |36| |72|         The 3-streams
  :    :    :    :

现在,我们要操纵这个,以便随时 3流将在2流中订购 关于他们的第一要素。 结果是下一个最小的生成数 将始终是第一个3流的第一个元素。

因此,要获得您希望获得的序列中的下一个数字, 我们将推出第一个3流, 拉出它的第一个元素(这是我们感兴趣的数字), 然后在2流中重新插入3流 在由 new 第一个元素确定的位置。 第一个数字(1)被提取后的新状态将是:

---------------------- --- -- -
| The 2-stream ...
---|----|----|----|---- --- -- -
   V    V    V    V
 | 2| | 3| | 4| | 8|
 | 6| | 9| |12| |24| ...
 |18| |27| |36| |72|         The 3-streams
  :    :    :    :

请注意,此方法不依赖于2 ^ i,3 ^ j或乘法 (仅在2 ^ i * 3 ^ j随着i和j单调增加)。 我发布了另一个答案,并且 结果更加简单快捷不相信我:它与数学无关

以下是使用SRFI-41流的示例实现:

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

使用plt-scheme(在我相当蹩脚的硬件上):

$ 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

我想,可以用生成器实现这个, 但棘手的部分是实施(insert)。 你可以通过组合发电机来做到这一点, 但是每次拉出一个数字时你最终都会添加一个“图层”, 而使用(insert)创建的流与原始流共享尾部 (“层”最终合并)。

答案 2 :(得分:1)

至少如果我理解你的问题,你只需要合并两个生成器的结果:

  1. 从每个生成器生成输出
  2. 生成两者中较小的一个作为下一个输出
  3. 从该生成器生成下一个输出
  4. 返回第2步

如果两个生成器生成相等的值,则将其生成为输出,并从每个生成器生成下一个值。

请注意,虽然它通常用于排序现有数据而不是生成新数据,但这类似于普通合并排序中使用的合并,除了我假设您不需要重复项,其中合并排序通常会保留重复。

编辑:感谢lpthnc,我重读了这个问题,我认为他是对的 - 我误读了原来的问题。要获得正确的输出,您需要创建第三个生成器并生成(在这种情况下)6的倍数,并使用该结果集与其他两个生成器之间的三向合并。

我没有玩过多少,但我相信PLT Scheme最近迭代中的懒惰语言级别(或懒惰模块)会让你编写代码来生成整个无限序列,这理论上会使用无限时间和记忆,但只根据需要评估其有限子集。

答案 3 :(得分:1)

没有任何示例的简单解决方案是创建一个新的。

for (i = 0; i < X; i++)
{
 if (i%2 or i%3)
 {
  cout << i
 }
}

编辑:X是你要运行多长时间,说你想要输出0-100把100。

int counter = 1000;
bool done = false;
while(!done)
{
 if (i%2 or i%3)
 {
  cout << i;
  counter--;
  if(counter <= 1)
   {
     done = true;
   }
 }
i++;
}

这有点乱,但应该有用。

编辑:计数器应该以1结束,否则它将为您提供1001项。

答案 4 :(得分:1)

只需合并两个有序列表 a la

(define merge
  (lambda (pred ls1 ls2)
    (cond
      [(null? ls1) ls2]
      [(null? ls2) ls1]
      [(pred (car ls1) (car ls2))
       (cons (car ls1) (merge pred (cdr ls1) ls2))]
      [else (cons (car ls2) (merge pred ls1 (cdr ls2)))])))

here解除。

答案 5 :(得分:1)

数据被编辑。我越看这一点,我就越认为我错了 - 其他人似乎已经有了更好的答案。

对不起,这些都不是方案,只是伪代码......

以下代码与我从您的问题中获得的思维过程相匹配:

编辑:修改后的伪代码现在我意识到它是“2 ^ i * 3 ^ j”,而不是“2 ^ i,3 ^ j”

 // If i got close, this time,
 // inputs min-i=0, max-i=2, min-j=0, max-j=2
 // should get output like
 //  2^0 * 3^0 = 1
 //  2^0 * 3^1 = 3
 //  2^0 * 3^2 = 6
 //  2^1 * 3^0 = 2
 //  2^1 * 3^1 = 6
 //  2^1 * 3^2 = 12
 //  2^2 * 3^0 = 4
 //  2^2 * 3^1 = 12
 //  2^2 * 3^2 = 24

 LET min-i, max-i, min-j, max-j be input
 LET current-value = 1

 FOR i = min-i to max-i
   FOR j = min-j to max-j DO
     PRINT "2^" . i . " * j^" . j . " = " . current-value
     current-value *= 3;
   DONE // end j loop

   current-value *= 2
 DONE // end i loop

答案 6 :(得分:1)

在Haskell中这很容易:

merge as bs =
  case (as, bs) of
    ([], _) -> bs
    (_, []) -> as
    ((a:as'), (b:bs')) ->
      if a <= b
        then a : (merge as' bs)
        else b : (merge as bs')
rmDups as =
  case as of
    [] -> []
    [a] -> [a]
    (a:bs@(b:_)) ->
      if a == b
        then rmDups bs
        else a:(rmDups bs)
take 25 $ rmDups $ merge (map (2^) [1..]) (map (3^) [1..])

产生以下结果:

[2,3,4,8,9,16,27,32,64,81,128,243,256,512,729,1024,2048,2187,4096,6561,8192,16384,19683,32768,59049]

虽然我认为有更优雅的方式来做...