Generate Prime Numbers with Python 
Tags: python, math
Published: 2021-02-24 17:13:00

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)

```python
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    
```
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.

```python
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]
```

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] (https://towardsdatascience.com/memoization-in-python-57c0a738179a) 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**.

```python
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.
``` 

And [**this one**](http://recmath.org/Magic%20Squares/moreprimes.htm#Primes%20Adjacent%20to%206n) 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,<br>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.

```python
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
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qom213o9em2obycr7sdi.png)
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<br>square-root of N  |     2,3,...,7,8    |    7   |
|   C  | set of primes below<br>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.

```python
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
``` 
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,<br>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.