Skip to content

Commit a3f5a55

Browse files
committed
enh: improve primes()
1 parent 2fd2e0c commit a3f5a55

3 files changed

Lines changed: 28 additions & 9 deletions

File tree

unpythonic/mathseq.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"cauchyprod", "diagonal_reduce",
2626
"fibonacci", "primes"]
2727

28-
from itertools import repeat, chain, count, product, takewhile
28+
from threading import RLock
29+
from itertools import repeat, chain, count, takewhile
2930
from operator import add as primitive_add, mul as primitive_mul, \
3031
pow as primitive_pow, mod as primitive_mod, \
3132
floordiv as primitive_floordiv, truediv as primitive_truediv, \
@@ -722,17 +723,32 @@ def fibonacci():
722723
yield a
723724
a, b = b, a + b
724725

726+
# Manual memoization for speed. One global memo to save memory when several
727+
# instances of primes() are created in the same session.
728+
_primesmemo = [2, 3]
729+
_primeslock = RLock()
725730
def primes():
726731
"""Return the prime numbers 2, 3, 5, 7, 11, 13, ... as a lazy sequence.
727732
728733
Implementation is based on an FP version of the sieve of Eratosthenes,
729734
with internal memoization.
730735
"""
731-
memo = [] # we memoize manually for speed (see test_gmemo.py)
736+
# TODO: utilize the memo also when already in the loop.
732737
def memoprimes():
733-
for n in chain([2, 3, 5, 7], (sum(xs) for k in count(10, step=10)
734-
for xs in product([k], [1, 3, 7, 9]))):
735-
if not any(n % p == 0 for p in takewhile(lambda x: x*x <= n, memo)):
736-
memo.append(n)
738+
# to avoid a race condition, we must know the last p actually yielded by us
739+
# (since some other thread may insert a new entry to _primesmemo between
740+
# our last yield and when we read _primesmemo[-1] for the divmod check)
741+
for p in _primesmemo:
742+
yield p
743+
# find the next candidate
744+
decade, mod = divmod(p, 10)
745+
inits = range(mod + 2, 10, 2) if mod < 9 else []
746+
inits = (10*decade + k for k in inits)
747+
for n in chain(inits, (d + k for d in count(10*(decade + 1), step=10)
748+
for k in [1, 3, 7, 9])):
749+
if not any(n % p == 0 for p in takewhile(lambda x: x*x <= n, _primesmemo)):
750+
with _primeslock: # lock to make test-and-update atomic.
751+
if _primesmemo[-1] < n:
752+
_primesmemo.append(n)
737753
yield n
738754
return m(memoprimes())

unpythonic/test/test_gmemo.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
from itertools import count, takewhile, product, chain
3+
from itertools import count, takewhile, chain
44
from collections import Counter
55

66
from ..gmemo import gmemoize, imemoize, fimemoize
@@ -137,8 +137,8 @@ def mprimes():
137137
@gmemoize # skip testing 15, 25, 35, ...
138138
def mprimes2():
139139
yield 2
140-
for n in chain([3, 5, 7], (sum(xs) for k in count(10, step=10)
141-
for xs in product([k], [1, 3, 7, 9]))):
140+
for n in chain([3, 5, 7], (d + k for d in count(10, step=10)
141+
for k in [1, 3, 7, 9])):
142142
if not any(n % p == 0 for p in takewhile(lambda x: x*x <= n, mprimes2())):
143143
yield n
144144

unpythonic/test/test_mathseq.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ def ulp(x): # Unit in the Last Place
219219
assert tuple(take(10, primes())) == (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
220220
assert tuple(take(10, fibonacci())) == (1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
221221

222+
assert tuple(take(20, primes())) == tuple(take(20, primes())) # caching
223+
assert last(take(21, primes())) == 73
224+
222225
print("All tests PASSED")
223226

224227
if __name__ == '__main__':

0 commit comments

Comments
 (0)