Example code - Classes

From 22113
Revision as of 11:04, 20 March 2024 by WikiSysop (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

You know how I often use primes in my examples, so I decided to make a prime number generator class as an example.
It is able to generate a range of primes, somewhat similar to the way range() works.
Wanting to be smart, I decided to store the primes I have already computed in a class variable, so I do not have to compute them again at least while the program runs.

I am doing a running incremental calculation of primes, storing them as I find them, and using them in my computation for the next prime. This is probably the most efficient naive implementation of finding primes. Better solutions require mathematical theories about primes - not going there. All I work with is the fact that primes are only divisible by 1 and the prime itself. Anything else in this code is deduction and inference.

This is a type of performance improvement called caching, i.e. remembering your results, so they can be reused.
It is possible to do something similar when computing the factorial - using a dict to store the results. The key observation to see this is possible, is that if you calculated 5!, you have also calculated 4!, 3!, 2!, 1! on the way. You might as well save these results, so you do not have to compute them again.

#!/usr/bin/env python3
# Prime number generator

class PrimeGenerator:
    # Class varible, known primes in consecutive order, can be extended, but must contain these
    knownprimes = [2, 3]
    # Highest tested number for prime
    highesttested = 3

    # Instatiation
    def __init__(self, number=None):
        if number is not None:
            # Don't catch an exception below - it does want we want.
            number = int(number)
        self.target = number                
    
    # Initializing iteration
    def __iter__(self):
        if self.target is None:
            raise ValueError("No number specified")
        self.pos = 0
        return self
    
    # Find next prime
    def __next__(self):
        # Can we use the list of known primes to find the next?
        if self.pos < len(self.knownprimes):
            nextprime = self.knownprimes[self.pos]
            if nextprime >= self.target:
                raise StopIteration
            self.pos += 1
            return nextprime
        # No, start computing the next prime
        while self.target > PrimeGenerator.highesttested+1:
            PrimeGenerator.highesttested += 1
            if self._isprime(PrimeGenerator.highesttested):
                self.knownprimes.append(PrimeGenerator.highesttested)
                self.pos += 1
                return self.highesttested
        raise StopIteration

    # Private method for identifying a prime
    def _isprime(self, number):
        factor = 0
        pos = 0
        while factor*factor <= number:
            # find next potential factor either in known primes or odd numbers above last known prime
            if pos < len(self.knownprimes):
                factor = self.knownprimes[pos]
                pos += 1
            else:
                factor += 2
            # test if it truly is a factor
            if number % factor == 0:
                return False
        return True

    # It is nice be able to ask if an number is a prime
    def isprime(self, number=None):
        if number is None:
            number = self.target
        if number is None:
            raise ValueError("Number expected")
        if number in PrimeGenerator.knownprimes:
            return True
        if number <= PrimeGenerator.highesttested:
            return False
        return self._isprime(number)

if __name__ == "__main__":
    # Small testing
    for i in PrimeGenerator(1000):
        print(i)

    print(PrimeGenerator().isprime(1000000016531)) # True
    print(PrimeGenerator().isprime(1000000016521)) # False

    # Big prime, don't try
    # 999296950101072104250052714631