簡體   English   中英

在 Python 中合並惰性流(使用生成器)

[英]Merge of lazy streams (using generators) in Python

我在玩 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)))))

你的算法不正確。 您的m2, m3, m5應該縮放hamming_numbers ,而不是integers

主要問題是:您的merge()無條件地為其兩個參數調用next() ,因此兩者都前進了一步。 因此,在生成第一個數字后,例如m23生成器的2 ,在下一次調用時,它將第一個參數視為4(,6,8,...) ,第二個參數為6(,9,12,...) 3已經沒了。 所以它總是拉它的兩個參數,並且總是返回第一個的頭部( http://ideone.com/doeX2Q 上的測試條目)。

調用iter()完全是多余的,它在這里沒有任何添加。 當我刪除它( http://ideone.com/7tk85h )時,程序的工作原理完全相同,並產生完全相同(錯誤)的輸出。 通常iter()用於創建一個惰性迭代器對象,但它的參數在這里已經是這樣的生成器。

也不需要在您的sieve()調用iter()http://ideone.com/kYh7Di )。 sieve()已經定義了一個生成器,而 Python 3 中的filter()從一個函數和一個可迭代對象(生成器可迭代的)創建了一個迭代器。 另請參見例如Python 的生成器和迭代器之間的差異

我們可以像這樣進行合並,而不是:

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 

這是現在很多很多,有效(參見: 解釋的輸出素數的流Haskell代碼這個塊)。

遞歸與否,只是代碼的句法特征。 實際的運行時結構是相同的filter()適配器被提升到輸入流的頂部——要么是在適當的時刻,要么是太快了(所以我們最終會得到太多)。

我將提出這種不同的方法 - 使用 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]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM