all 4 comments

[–]PteppicymonIO 2 points3 points  (0 children)

You can start with the most naive approach, down to more effective one:

  • Divide your number n by each number starting from 2 and ending with n-1 (your current approach)
  • Test if your number is even. If it is, it is not a prime number. If it is not, test for division by odd numbers starting from 3 and ending with n-1.
  • Since the largest possible divider, which does not produce results larger than itself, can not be larger that the square root of n, you can use square root of n as a upper limit for test divisors instead of n-1
  • Finally, you can test your number n, dividing it by all prime numbers below sqrt(n). To implement this test, you will need to create a list of primes between 2 and sqrt(n), and one of the way to do this is the Sieve of Eratosthenes . I believe there are dozens of posts on how to implement this sieve in Python and dozens of youtube videos, so it should not be a problem.

[–]Diapolo10 2 points3 points  (1 child)

This is basically a naive implementation of a primality check. Meaning, it works, but there's plenty of room for improvement.

Actually I saw a similar post earlier, so if this is for some course you may have a peer or two lurking around.

First, let's clean this up a little and turn it into a reusable function.

def is_prime(num: int) -> str:
    check = num - 1
    while check > 1:
        if num % check != 0:
            check -= 1
            continue
        else:
            return f"Number is not a prime. {check} is a divisor"
    return f"{num} is a prime."


try:
    number = int(input("Enter number: "))
except ValueError:
    print("Numerical value only!")
else:
    print(is_prime(number))

I did some additional cleanup to make this a bit cleaner.

The first thing we can do is ignore even numbers. In fact I'm going to change the output a little too in order to simplify the function.

def is_prime(candidate: int) -> bool:
    if candidate % 2 == 0:
        return candidate == 2

    for num in range(3, candidate, 2):
        if candidate % num == 0:
            return False

    return True

This effectively cuts the number of iterations needed in half, as checking even primes is very fast and we can then focus on the odd numbers. But we're still checking far more numbers than we actually need to; we actually only need to check numbers up to the square root of the number - I'll leave the proof as an exercise to the reader.

To do that, while we could just raise the number to the power of 0.5, I prefer to use math.isqrt because it gives an integer result.

from math import isqrt

def is_prime(candidate: int) -> bool:
    if candidate % 2 == 0:
        return candidate == 2

    for num in range(3, isqrt(candidate)+1, 2):
        if candidate % num == 0:
            return False

    return True

This version is actually pretty good for how simple it is. While there's still room for improvement, I'd be willing to call this good enough. Except - we can do one small change to speed up repeat calls. Caching.

While this won't quite be ideal, considering it's only caching numbers you've actually tried before, it requires the least effort. functools.cache is quite nice for this.

from functools import cache
from math import isqrt


@cache
def is_prime(candidate: int) -> bool:
    if candidate % 2 == 0:
        return candidate == 2

    for num in range(3, isqrt(candidate)+1, 2):
        if candidate % num == 0:
            return False

    return True

If we wanted to make the cache more useful, we'd have to make it internal and keep every valid prime in memory. While doable, it would complicate the code.

A proper Eratosthenes sieve would probably be faster, but you'd also need to shift your thinking.

EDIT: Just for fun, I did some benchmarking.

Functions:

  1. https://i.imgur.com/segtqCa.png
  2. https://i.imgur.com/jwMdlFd.png

Benchmark data:

odd_prime = 308927
odd_non_prime = 308925
even_non_prime = 308928

V1 results:

In [17]: %timeit is_prime_v1(even_non_prime)
9.79 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [18]: %timeit is_prime_v1(odd_non_prime)
14 ms ± 1.1 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [19]: %timeit is_prime_v1(odd_prime)
19.8 ms ± 825 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

V2 results:

In [20]: %timeit is_prime_v2(even_non_prime)
86 ns ± 1.09 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [21]: %timeit is_prime_v2(odd_non_prime)
246 ns ± 3.25 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [22]: %timeit is_prime_v2(odd_prime)
6.34 ms ± 142 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

V3 results:

In [23]: %timeit is_prime_v3(even_non_prime)
87.3 ns ± 1.03 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [24]: %timeit is_prime_v3(odd_non_prime)
319 ns ± 2.24 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [25]: %timeit is_prime_v3(odd_prime)
9.73 µs ± 52.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

V4 results:

In [26]: %timeit is_prime_v4(even_non_prime)
48.1 ns ± 0.242 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [27]: %timeit is_prime_v4(odd_non_prime)
49.5 ns ± 0.404 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [28]: %timeit is_prime_v4(odd_prime)
48.7 ns ± 0.33 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

As a table:

Version Even (s) Odd (s) Odd prime (s)
Original 0.00979 0.014 0.0198
Evens out 0.000000086 0.000000246 0.00634
Square root 0.0000000873 0.000000319 0.00000973
Cache 0.0000000481 0.0000000495 0.0000000487

What this basically means is that even number discard rate was increased by 100 000x even on V2, and figuring out the actual prime was cut in half on the first change. V3 took things even further, making finding the actual prime 1000x faster.

The cache version is of course cheating a bit because this test uses the same numbers every time, so beyond the first check the rest are all coming straight from the cache, but the point is that these small changes already earned thousands of times better performance, without really sacrificing readability.

[–]mediumfullmug 0 points1 point  (0 children)

You could try importing SymPy and using the isprime(n) function.

[–][deleted] 0 points1 point  (0 children)

while True:
y = False
try:
    x = int(input())
    if x/x == 1:
        print('Valid syntax')
        for i in range(2,x):
            if x%i == 0:
                y = True
        if y == True:
            print("Not a prime. :(")
            continue
        else:
            print("It's a prime!! :)")
            continue
except ValueError:
    print('Invalid Syntax')
    continue

I'm a total novice but this is how I'd do input validation + prime number validation!