DEV Community

Ahmad Rafsanjani
Ahmad Rafsanjani

Posted on

Generate Prime Numbers with Python

Hello everyone. I am Ahmad, a noobie here.
Today I'm gonna share a bit of my Python coding experience.
As of you or any other programmer did, i too learned to solve prime number with coding in my early months of learning programming.

In my case I used Python which has easy-to-understand syntax, compared like to my first coding language C++ which at that time I only understand a bit about it.

Back to the topic, my first prime solving program is to print the result whether an integer input is prime number or not.

A prime number (or a prime) is a natural number greater than 1 that is not a product of two smaller natural numbers. (Wikipedia)

def isPrime(N):
    for x in range(2,N):    #numbers from 2 to N-1
        if N%x == 0:
            return False
    return True

isPrime(17)      #output: True
isPrime(9)       #output: False    
Enter fullscreen mode Exit fullscreen mode

Then, using the function above as a base I was going deeper with making new function that print out all prime numbers below an integer input.

def printPrimes(M):
    for x in range(2,M):
        if isPrime(x):
            print(x, end=" ")

printPrimes(20)    #output: 2 3 5 7 11 13 17 19

#Later on, I found this one-liner online, pretty cool right
primeList= lambda M: [x for x in range(2,n) if all(x%k for k in range(2,x))]
primeList(20)      #output: [2, 3, 5, 7, 11, 13, 17, 19]
Enter fullscreen mode Exit fullscreen mode

I guess you all had used the same algorithm in your first prime solving program. Back then, I never try to find out "Is a certain big number prime or not?" or "how many primes are there below a certain big number?". My curiosity just stop at number 100.

But recently, about a half year ago I found this article by Sadrach Pierre, Ph.D about memoization in fibonacci sequence. I learned first time about caching and its effect to speed and performance here. Also about there's a limit of interpreter can compute due to memory issue and timeout limit.

Yes, it's challenging to see how fast and efficient our code could run. And I started it by challenging Prime Numbers. Then I found this one.

def isPrime(N):
    for i in range(2,int(N**0.5)+1):   #numbers from 2 to square-root of N
        if N%i==0:
            return False
    return True

#It's similiar with the first isPrime function I learned, except the threshold factor being square-root of N.
#Iteration of computation is also decreased to about square-root of N. It means the function is faster.
#Though at that time I don't really know where this root-square came from.
Enter fullscreen mode Exit fullscreen mode

And this one about prime number from adjacent of 6n. Aside from 2 and 3, all primes satisfy this pattern, whether being 6n+1 or 6n+1. But not all this adjacents are prime.
6x(1)+1 = 7 :prime
6x(1)-1 = 5 :prime
6x(4)+1 = 25 :not prime
6x(4)-1 = 23 :prime

A little explanation by example about square-root-of-N-Threshold, see example composite numbers in table below

20 16 27 36
1 x 20 1 x 16 1 x 27 1 x 36
2 x 10 2 x 8 3 x 9 2 x 24
4 x 5 4 x 4 3 x 12
4 x 9
6 x 6

As you can see above, when the left-number increase, the right-number decrease. The left-number never been greater than square-root of N.

Now we can narrow down the candidates of prime numbers from all numbers below M to odd numbers below M then to adjacent of 6n. See the comparison table( case example for M=50 ).

Prerequisites members below 50 Quants Annotation
all numbers (2 to M) 2,3,4,...,48,49 48 All in scope
odd numbers (2 to M) 3,5,7,...,47,49 24 2 not included
adjacent of 6n,
for n start from 1
5,7,11,13,...,49 16 2,3 not included

With adjacent of 6n pattern, we can minimize the numbers of candidates to to a certain degree. Now it's time to transpire it into python code.

def isPrime(N):
    for i in range(2,int(N**0.5)+1):
        if N%i==0:
            return False
    return True

def primeGenerator(M):
    result= [2,3]
    maxn= M//6
    n=1
    while n<=maxn:
        a= 6*n - 1
        if isPrime(a):
            result.append(a)
        b= 6*n + 1
        if b>=M:
            break
        else:
            if isPrime(b): result.append(b)
        n+=1
    return result
Enter fullscreen mode Exit fullscreen mode

Function above has two type of approach in speeding up. First approach is narrowing the dividing factor (to square-root of N) and second approach is narrowing the candidates (to adjacent of 6n). Next question, Can We make it even faster?

Ofcourse we can. This time we won't do improvement in second type of approach( cause I still have no idea ), instead we'll focus on first type of approach( narrowing the dividing factor ). See this table based on candidates from adjacent of 6n.
Alt Text
The highlighted items is composite numbers and its factors. Do you see the similiarity between them. Yess, the factor is came from prime as well. What does it mean? It means we can narrow it again, from all numbers below N to all numbers below square-root of N then finally to a set of primes.

Type Prerequisities members for N= 71 Quants
A all numbers below N 2,3,4,...,70 69
B all numbers below
square-root of N
2,3,...,7,8 7
C set of primes below
square-root of N
2,3,5,7 4
D Type C exclude 2,3 5,7 2

We're using type B in previous primeGenerator function, if we can apply type D above our app will boost, definetely. Well, it's a bit tricky to apply this, because we need dynamic array for caching set of primes as dividing factors.

def fastGenerator(M):
    result=[2,3]
    a=5
    b=7
    count=3
    factor=[5]       #caching factor
    threshold=25     #instead of calculating square-root on each candidate,
    while a<M:       #...I change threshold every other time
        if a==threshold:
            factor.append(result[count])
            threshold=factor[-1]**2
            count+=1
        elif all(a%k for k in factor):
            result.append(a)
        if b>=M:
            break
        elif b==threshold:
            factor.append(result[count])
            threshold=factor[-1]**2
            count+=1
        elif all(b%k for k in factor):
            result.append(b)
        a+=6
        b+=6
    return result
Enter fullscreen mode Exit fullscreen mode

Here is a performance speed test using timeit function.

Name Features 20K 100K 500K 1000K 2000K 5000K
primeList Standard 2.59s 62.7s
primeListCache Caching factor 0.34s 5.65s 115.3s
primeGenerator Adjacent 6n candidates,
Square-Root of M factor
0.03s 0.28s 2.38s 7.01s 17.23s 76.2s
fastGenerator same as above plus caching 0.76s 1.88s 5.08s 18.83s

I stop testing the function if it passed 50s. I didn't include first two test-variable in fastGenerator because I guess it will be too small. I'll make sure I'll be more detailed in the future and not skipped even a tiny testing-variable.

Conclusion

We can speeding up our high-consumed memory computation by eleminating unnecessary computation. In above case we eliminate many iterations from two types of approach. From the numbers of candidates and from the numbers of dividing factor.

The important part of speeding up the app is you must understand very well about the topic of your app, then do some research to understand it even more. By doing that you will find flaws in your algorithm as well as the possibility of improvement.

Closure

After completing this challenge, I feel proud to my self. It proved that I'm capable, and I start wondering if it just me, or is it me the first one to find this method of generating primes from big number, Oh my God am I a genius? OFCOURSE NOT! WAKE UP BOY..!

Later on, I found many articles about primes and several methods of generating primes. The same method as I used in above function was published decades ago. But it still somewhat Satisfying, I mean like breaking out your own record.

See you in another post, feel free to comment. And I apologize in advance if any of my words offense you. It's my first blog post anyway. Thank You.

Top comments (0)