[英]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.