## ## Name: prime.py ## Purpose: Implements the Sieve of Eratosthenes. ## Author: M. J. Fromberger ## ## To work with primes, create an instance of the Primes class, ## p = Primes() ## ## You can now index primes by position: ## p[0] ==> 2, p[1] ==> 3, etc. ## ## You can slice: ## p[:5] ==> [2, 3, 5, 7, 11] ## p[4:7] ==> [11, 13, 17] ## ## Test for primality as inclusion: ## 65537 in p ==> True ## 45 in p ==> False ## ## Find the index of a prime: ## p.find(25) ==> -1 ## p.find(5) ==> 2 ## ## Return the factorization of a value: ## p.factor(25) ==> yielding [5, 5] ## ## Iterate over the collection: ## for z in p: ## if z < 1000: print z ## else: break ## ## Clear out the cache ## p.clip() ## ## Factoring and primality testing are done using trial division, but ## with with a few efficiencies: Primes are only generated up to the ## square root of the target value, and only odd candidates are tried, ## which reduces the loop overhead. ## ## Note, however, that the .find() must generate all the primes less ## than or equal to the candidate in order to find the index, so it is ## going to be time-consuming for large candidates. Indexing and ## slicing have the same issue, although to a slightly lesser extent. ## class Primes ( object ): """An indexable generator of primes.""" def __init__(self): """Create a new instance of the generator.""" # The cache is seeded with 2 and 3 so that the sieve can skip # even candidates easily. Do not remove them, lest it fail. self._primes = [2, 3] self._pmap = {} self._pmap.update((q, p) for (p, q) in enumerate(self._primes)) def next(self): """Generate the next prime not already known.""" # This is mutually recursive with .factor(), but it is safe in # this case because all the candidate values are greater than # the largest prime already in the sieve at all times. c = self._primes[-1] + 2 while self.factor(c).next() <> c: c += 2 self._pmap[c] = len(self._primes) self._primes.append(c) return c def __contains__(self, prm): """Answers True if the given value is prime, otherwise False.""" return self.factor(prm).next() == prm def __getitem__(self, pos): """Get the pos'th prime, indexed from 0. This may be time-consuming for large indices.""" if isinstance(pos, int): if pos < 0: raise ValueError("index must be non-negative") while len(self._primes) <= pos: self.next() elif isinstance(pos, slice): while len(self._primes) < max(pos.start, pos.stop): self.next() return self._primes.__getitem__(pos) def __iter__(self): """Obtain a generator over all the primes.""" pos = 0 while True: while pos >= len(self._primes): self.next() yield self._primes[pos] pos += 1 def find(self, value): """Returns the prime index of the value, or -1 if the value is not prime. This operation may be time consuming for large values.""" while self._primes[-1] < value: self.next() return self._pmap.get(value, -1) def factor(self, value): """Return a generator for the prime factorization of value. Factors are returned in nondecreasing order. Factors by trial division, but efficiently.""" if value == 1 or value in self._pmap: yield value return for p in self: while value % p == 0: yield p value /= p else: if p ** 2 > value: if value > 1: yield value return def clip(self, size = 0): """Trim the cache of primes to no more than size elements.""" if size >= 0: self._primes = self._primes[:max(size, 2)] self._pmap = {} self._pmap.update((q, p) for (p, q) in enumerate(self._primes)) else: raise ValueError("size must be non-negative") # Here there be dragons