我正在使用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)))))
答案 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]