I have written a simple snippet of code that implements a function that returns a list of primes up to a certain integer n
. Assuming that this function could be called several times in the script for different values of n
, I wanted to make it as "lazy" as possible—that is, able to reuse previously-calculated variables.
Here is the code (assume isPrime(n)
returns True
if n
is prime and False
otherwise):
primes = []
def pi(n):
global primes
if len(primes) == 0:
# Full
for i in range(2, n + 1):
if isPrime(i):
primes.append(i)
return primes
elif n <= primes[-1]:
# Lazy
return list(filter(lambda m: m <= n, primes))
else:
# Semi-lazy
for i in range(primes[-1] + 1, n + 1):
if isPrime(i):
primes.append(i)
return primes
The problem is, as you may have spotted, that I'm using a global variable (primes
) to store the list of primes, and later modifying it inside the pi
function. I've heard that relying on mutable global variables is a bad design choice. What are better ways to implement this?
This question should actually be generalized to the following: what's the best design pattern to implement a 'lazy' function (i.e. a function that reuses previously calculated values)?
list(filter(lambda...))
can be inefficient too. Consider generating a few hundred million primes, and then asking for the list of primes up to 100. When thefilter()
reaches101
, it could stop, but won’t since it doesn’t know that.itertools.takewhile()
would be an improvement. Better still would bebisect
to find the endpoint, and then simply returning a slice. \$\endgroup\$filter
but didn't know/hadn't made research about better-fitting alternatives when quickly putting this together. I think in this case I'd usetakewhile
; it seems more 'human-readable' and straightforward to me. \$\endgroup\$return primes[:bisect.bisect(primes, n)]
. Just so you know. \$\endgroup\$