在Python中合并延迟流(使用生成器)

时间:2013-02-01 14:08:06

标签: python functional-programming generator primes hamming-numbers

我正在使用Python 3的功能,我试图实现用于计算汉明数的经典算法。这是主要因素只有2,3或5的数字。首先汉明数字是2,3,4,5,6,8,10,12,15,16,18,20等等。

我的实施如下:

def scale(s, m):
    return (x*m for x in s)

def merge(s1, s2):
    it1, it2 = iter(s1), iter(s2)
    x1, x2 = next(it1), next(it2)
    if x1 < x2:
        x = x1
        it = iter(merge(it1, s2))
    elif x1 > x2:
        x = x2
        it = iter(merge(s1, it2))
    else:
        x = x1
        it = iter(merge(it1, it2))
    yield x
    while True: yield next(it)

def integers():
    n = 0
    while True:
        n += 1
        yield n

m2 = scale(integers(), 2)
m3 = scale(integers(), 3)
m5 = scale(integers(), 5)

m23 = merge(m2, m3)

hamming_numbers = merge(m23, m5)

合并的问题似乎不起作用。在此之前,我以相同的方式实施了Eratosthenes筛选,并且它完全可以正常运行:

def sieve(s):
    it = iter(s)
    x = next(it)
    yield x
    it = iter(sieve(filter(lambda y: x % y, it)))
    while True: yield next(it)

这个使用与合并操作相同的技术。所以我看不出有任何区别。你有什么想法吗?

(我知道所有这些都可以通过其他方式实现,但我的目标是完全理解Python的生成器和纯函数功能,包括递归,而不使用类声明或特殊的预构建Python函数。)

UPD:对于Will Ness,这是我在LISP(实际上是Racket)中实现这种算法的方法:

(define (scale str m)
  (stream-map (lambda (x) (* x m)) str))

(define (integers-from n)
  (stream-cons n
               (integers-from (+ n 1))))

(define (merge s1 s2)
  (let ((x1 (stream-first s1))
        (x2 (stream-first s2)))
    (cond ((< x1 x2)
           (stream-cons x1 (merge (stream-rest s1) s2)))
          ((> x1 x2)
           (stream-cons x2 (merge s1 (stream-rest s2))))
          (else
           (stream-cons x1 (merge (stream-rest s1) (stream-rest s2)))))))


(define integers (integers-from 1))

(define hamming-numbers
  (stream-cons 1 (merge (scale hamming-numbers 2)
                        (merge (scale hamming-numbers 3)
                               (scale hamming-numbers 5)))))

2 个答案:

答案 0 :(得分:2)

您的算法不正确。您的m2, m3, m5应该缩放hamming_numbers,而不是integers

主要问题是这样:您的merge()无条件地调用next()这两个论点,因此 两者 提前一步。因此,在产生第一个数字之后,例如2生成器的m23,在下次调用时,它将第一个参数视为 4(,6,8,...) ,第二个参数为 6(,9,12,...) 即可。 3已经消失了。所以它总是拉出它的两个参数,并且总是返回第一个的头部(http://ideone.com/doeX2Q处的测试条目)。

调用iter()完全是多余的,它在这里没有任何补充。当我删除它(http://ideone.com/7tk85h)时,程序完全相同并产生完全相同(错误)的输出。通常iter()用于创建一个惰性迭代器对象,但它的参数已经是这样的生成器。

您也无需在iter()http://ideone.com/kYh7Di)中致电sieve()sieve()已经定义了一个生成器,Python 3中的filter()从一个函数和一个iterable(生成器 可迭代)创建一个迭代器。另见例如Difference between Python's Generators and Iterators

我们可以像这样进行合并,而不是:

def merge(s1, s2):
  x1, x2 = next(s1), next(s2)
  while True:
    if x1 < x2:
        yield x1
        x1 = next(s1)
    elif x1 > x2:
        yield x2
        x2 = next(s2)
    else:
        yield x1
        x1, x2 = next(s1), next(s2)

递归本身对于定义sieve()函数也是非必要的。事实上,它只会掩盖代码的巨大缺陷。它产生的任何素数将由其下面的所有素数值进行测试 - 但只有那些低于其平方根的素数才真正需要。我们可以很容易地以非递归样式(http://ideone.com/Qaycpe)修复它:

def sieve(s):    # call as: sieve( integers_from(2))
    x = next(s)  
    yield x
    ps = sieve( integers_from(2))           # independent primes supply
    p = next(ps) 
    q = p*p       ; print((p,q))
    while True:
        x = next(s)
        while x<q: 
            yield x
            x = next(s)
        # here x == q
        s = filter(lambda y,p=p: y % p, s)  # filter creation postponed 
        p = next(ps)                        #   until square of p seen in input
        q = p*p 

现在很多,很多更有效率(另请参阅:Explain this chunk of haskell code that outputs a stream of primes)。

递归与否,只是代码的语法特征。实际的运行时结构是相同的 - filter()适配器在输入流的顶部被提升 - 在适当的时刻,或者太快(因此我们最终得到的方式太多了)

答案 1 :(得分:0)

我将提出这种不同的方法-将Python heapq(min-heapq)与生成器(惰性评估)一起使用(如果您不坚持保留merge()函数)

from heapq import heappush, heappop

def hamming_numbers(n):
    ans = [1]

    last = 0
    count = 0

    while count < n:
        x = heappop(ans)

        if x > last:
            yield x

            last = x
            count += 1

            heappush(ans, 2* x)
            heappush(ans, 3* x)
            heappush(ans, 5* x)


    >>> n  = 20
    >>> print(list(hamming_numbers(20)))
       [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36]