Created
March 25, 2021 18:57
-
-
Save sosi-deadeye/f0f592e6de54570060476ff8c5ad8094 to your computer and use it in GitHub Desktop.
made it more pythonnic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Python Prime Sieve | |
MyFirstPython Program (tm) Dave Plummer 8/9/2018 | |
This is the main prime_sieve class. Call it with the number you wish as an upper limit, then | |
call the run method to do the calculation. print_results will dump the count to check validity. | |
Updated 3/22/2021 for Dave's Garage episode comparing C++, C#, and Python | |
Updated 3/22/2021 more Pythonic | |
""" | |
import timeit | |
from math import sqrt | |
class PrimeSieve: | |
prime_counts = { | |
10: 1, | |
100: 25, | |
1000: 168, | |
10000: 1229, | |
100000: 9592, | |
1000000: 78498, | |
10000000: 664579, | |
100000000: 5761455, | |
} | |
def __init__(self, limit: int): | |
self.sieve_size = limit | |
self.raw_bits = [True] * ((self.sieve_size + 1) // 2) | |
def validate_results(self) -> bool: | |
""" | |
Check to see if this is an upper_limit we can | |
""" | |
if self.sieve_size in self.prime_counts: | |
# the data, and (b) our count matches. Since it will return | |
return self.prime_counts[self.sieve_size] == self.count_primes() | |
# false for an unknown upper_limit, can't assume false == bad | |
return False | |
def get_bit(self, index: int) -> bool: | |
""" | |
Gets a bit from the array of bits, but automatically just filters out even numbers as false, | |
and then only uses half as many bits for actual storage | |
""" | |
# even numbers are automatically returned as non-prime | |
if index % 2 == 0: | |
return False | |
else: | |
return self.raw_bits[index // 2] | |
def clear_bit(self, index: int) -> None: | |
""" | |
Reciprocal of get_bit, ignores even numbers and just stores the odds. Since the prime sieve work should | |
never waste time clearing even numbers, this code will assert if you try to | |
""" | |
if index % 2 == 0: | |
return | |
self.raw_bits[index // 2] = False | |
def run(self) -> None: | |
""" | |
Reciprocal of get_bit, ignores even numbers and just stores the odds. Since the prime sieve work should | |
never waste time clearing even numbers, this code will assert if you try to | |
""" | |
factor = 3 | |
q = sqrt(self.sieve_size) | |
while factor < q: | |
for num in range(factor, self.sieve_size): | |
if self.get_bit(num): | |
factor = num | |
break | |
# If marking factor 3, you wouldn't mark 6 (it's a multiple of 2) so start with the 3rd instance of this | |
# factor's multiple. We can then step by factor * 2 because every second one is going to be even by | |
# definition | |
for num in range(factor * 3, self.sieve_size, factor * 2): | |
self.clear_bit(num) | |
# No need to check evens, so skip to next odd (factor = 3, 5, 7, 9...) | |
factor += 2 | |
def count_primes(self) -> int: | |
""" | |
Return the count of bits that are still set in the sieve. Assumes you've already called | |
run, of course! | |
""" | |
# yes, you can calculate with booleans | |
# True == 1 and False == 0 | |
return sum(self.raw_bits) | |
def print_results(self, show: bool, duration: float, passes: int) -> None: | |
""" | |
Displays the primes found (or just the total count, depending on what you ask for) | |
""" | |
# Since we auto-filter evens, we have to special case the number 2 which is prime | |
if show: | |
# you've control what is concatenated to the end of the str | |
# an empty str will get rid of the default newline | |
print("2, ", end="") | |
count = 1 | |
# Count (and optionally dump) the primes that were found below the limit | |
for num in range(3, self.sieve_size): | |
if self.get_bit(num): | |
if show: | |
print(f"{num}, ") | |
count += 1 | |
assert count == self.count_primes() | |
print() | |
print("Passes:", passes) | |
print("Time:", duration) | |
print("Avg:", duration / passes) | |
print(f"Limit:", self.sieve_size) | |
print(f"Count: {count}") | |
print(f"Valid:", self.validate_results()) | |
# if this source code is imported from another python module | |
# the __name__ != __main__ | |
# and it will prevent to execute the code | |
# this let you run the script from commandline | |
# but you can also import this code here and | |
# reuse it in another Python module. | |
if __name__ == "__main__": | |
print("Please wait ⌛") | |
# Record our starting time | |
tStart = timeit.default_timer() | |
# We're going to count how many passes we make in fixed window of time | |
passes = 0 | |
# Calc the primes up to a million | |
# Reusing the instance saves time | |
sieve = PrimeSieve(1000000) | |
# Run until more than 10 seconds have elapsed | |
while timeit.default_timer() - tStart < 10: | |
# Find the results | |
sieve.run() | |
# Count this pass | |
passes += 1 | |
# After the "at least 10 seconds", get the actual elapsed | |
tD = timeit.default_timer() - tStart | |
# Display outcome | |
sieve.print_results(True, tD, passes) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment