patternpythonModerate
Prime number checker in Python 3 - follow-up
Viewed 0 times
numberfollowpythonprimechecker
Problem
I have updated my code from the last time it got reviewed. If anyone has some more suggestions on how I can improve this and/or make it faster it would be appreciated!
def is_prime(num):
'''Calculates whether num is a prime number,
if it is return True otherwise return False'''
# SPECIALCASE: These are special cases that are not primes
if num % 2 == 0 and num != 2 or num <= 1:
return False
# Look for a factor
for i in range(3, int(num ** (0.5)) + 1, 2):
# If there is a factor
if num % i == 0:
# Its not a prime number
return False
# If there is no factors and its not a special case
return TrueSolution
- Overview
Your code is well written and what it does is clear. However I will argue that if you want a drastic increase in performance, you need to switch your algorithm. Testing primality can be done in polynomial time, but is generally a problem which requires sophisticated math. The problem can be solved in polynomal time as shown by the AKS_primality_test. However there are far faster algorithms than this one. As you can see, it is not the easiest thing to understand without a background in higher mathematics.
- A review of your algorithm
- Show a probabilistic approach
- Compare your algorithm, the probabilistic, and the
isprimefunction from theprimefacpackage.
- Review
As your code is fairly short, there is not much to comment on. I really like that you use a
docstring in the beginning of your code to document it. The code looks good.- Use the
if __name__ == "__main__":module. It makes it easier to test and easier to import.
- Some of the comments are unnecessary and there is too much space in the last
return.
- The
num
One way to handle this could be
try:
int(num)
except ValueError:
raise ValueError("Ops. Input must be a positive integer")
if int(num) != num or num < 0:
raise ValueError("Ops. Input must be a positive integer")
if num in [0, 1]: return FalseThe last line could also have been written as
if num <= 1: return FalseSince we have already tested that num => 0
. However I prefer the first version as it is much clearer what it actually checks. You should always try to make it as clear as possible what you want to catch.
1.1 Some improvements on the algorithm
Say you check some number with your algorithm. If the number is not divisible by \$3\$ you do not need to ever check any multiples of \$3\$ again. Yet your code checks \$6\$, \$9\$ and so forth. A better approach would be to iterate over the numbers not divisible by \$2\$ or \$3\$. This removes the checking of \$2/3\$ of all numbers in comparison to only removing \$1/2\$. So, what numbers are not divisible by \$3\$ or \$2\$? Well all numbers are on the form \$6k+1\$, \$6k+2\$, \$6k+3\$, \$6k+4\$ or \$6k+5\$. Since they are either \$0\$, \$1\$, \$2\$, \$3\$, \$4\$ or \$5\$ numbers away from being a multiple of \$6\$. This means that only numbers on the form \$6k+1\$ or \$6k+5\$ are not divisible by \$2\$ or \$3\$.
Implementation is simple increase counter by \$6\$, test \$+1\$ and \$+5\$.
primes_to_remove = [2, 3, 5]
for prime in primes_to_remove:
if num == prime: return True
elif num % prime == 0: return False
# Look for a factor
limit = int(num**(0.5))+1
for multiple in range(6, int(num**(0.5))+1, 6):
for k in [1, 5]:
# If there is a factor
if num % (multiple+k) == 0:
# Its not a prime number
return False
This can further be generalized by removing [2, 3, 5] as shown below
primes_to_remove = [2, 3, 5, 7, 11, 13, 19, 23, 29]
for prime in primes_to_remove:
if num == prime: return True
elif num % prime == 0: return False
# Look for a factor
limit = int(num**(0.5))+1
for multiple in range(30, int(num**(0.5))+1, 30):
for k in [1, 7, 11, 13, 19, 23, 29]:
# If there is a factor
if num % (multiple+k) == 0:
# Its not a prime number
return False
return True
This idea of removing the first primes can again be modified into removing [2,3,5,7] and then check the remaining numbers. However the advantages of this method quickly diminish for larger primes as we are removing fewer and fewer numbers. As a last example, it is possible to only iterate over the primes. From the primesieve library we can import a prime generator.
from primesieve import Iterator
def is_prime_primegenerator(num):
it = Iterator()
prime = it.next_prime()
# Iterate over the primes below sqrt(n)
limit = int(num**(0.5))+1
while prime < limit:
prime = it.next_prime()
if num % prime == 0:
print num, prime
return False
return True
This is the best we can do following down your path of checking primes from the smallest to the largest. It assumes however that the next_prime() is extremely fast. Which it luckily is.
Using the 235 version of the sieve above, it gave me a \$2.5\$ times speed increase of your code. Putting it all together gives
``def is_prime(num):
'''Calculates whether num is a prime number,
if it is return True otherwise return Flase'''
try:
int(num)
except ValueError:
raise ValueError("Ops. Input must be a positive integer")
if int(num) != num or num < 0:
raise ValueError("Ops. Input must be a positive integer")
# SPECIALCASE: These are special cases that are not primes
if num in [0, 1]: return False
primes_to_remove = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
for prime in primes_to_remove:
if num == prime: return True
Code Snippets
try:
int(num)
except ValueError:
raise ValueError("Ops. Input must be a positive integer")
if int(num) != num or num < 0:
raise ValueError("Ops. Input must be a positive integer")
if num in [0, 1]: return Falseif num <= 1: return Falseprimes_to_remove = [2, 3, 5]
for prime in primes_to_remove:
if num == prime: return True
elif num % prime == 0: return False
# Look for a factor
limit = int(num**(0.5))+1
for multiple in range(6, int(num**(0.5))+1, 6):
for k in [1, 5]:
# If there is a factor
if num % (multiple+k) == 0:
# Its not a prime number
return Falseprimes_to_remove = [2, 3, 5, 7, 11, 13, 19, 23, 29]
for prime in primes_to_remove:
if num == prime: return True
elif num % prime == 0: return False
# Look for a factor
limit = int(num**(0.5))+1
for multiple in range(30, int(num**(0.5))+1, 30):
for k in [1, 7, 11, 13, 19, 23, 29]:
# If there is a factor
if num % (multiple+k) == 0:
# Its not a prime number
return False
return Truefrom primesieve import Iterator
def is_prime_primegenerator(num):
it = Iterator()
prime = it.next_prime()
# Iterate over the primes below sqrt(n)
limit = int(num**(0.5))+1
while prime < limit:
prime = it.next_prime()
if num % prime == 0:
print num, prime
return False
return TrueContext
StackExchange Code Review Q#133280, answer score: 12
Revisions (0)
No revisions yet.