I am currently solving a mathematical problem in which I need to count the number of reduced proper fractions with both numerator and denominator more more than 1000000 (10^6).
I have code that works fine for small numbers; the example given (value=8) gives the correct (given) answer 21.
But this code seems to be very slow for big numbers for reasons I don't know. I read a whole bunch of similar questions on SO, but couldn't find anything that was useful. I had a closer look at this and this, but that didn't really help me out. My code works with acceptable speed for values until 1000, then it gets super-super-slow.
import math
def is_prime(n):
if n == 2:
return True
if n % 2 == 0 or n <= 1:
return False
sqr = int(math.sqrt(n)) + 1
for divisor in range(3, sqr, 2):
if n % divisor == 0:
return False
return True
def get_divisors(n):
liste = [1]
if n % 2 == 0:
liste.append(2)
for divisor in range(3, n+1):
if n % divisor == 0:
liste.append(divisor)
return liste
def intersect(a, b):
return list(set(a) & set(b))
until = 1000
primes = list()
for i in range(int(until)):
if i != 1 and i != 0:
if is_prime(i):
primes.append(i)
pos = 0
for i in range(1, until+1):
if i%50 == 0:
print(i)
if is_prime(i):
pos += (i-1)
else:
di = get_divisors(i)
di.remove(1)
for j in range(1, i):
dj = get_divisors(j)
if intersect(di, dj)==[]:
pos+=1
print(pos)
I want to know which parts of my program are reducing the speed and how to fix these issues.
If the primes themselves are fast enough (I'd still recommend an Eratosthenes sieve which is better than your okay prime testing to mass-generate primes), the generation of the divisors isn't
for divisor in range(3, n+1):
if n % divisor == 0:
liste.append(divisor)
this loop has O(n)
complexity when it could have O(n**0.5)
complexity like this:
liste = set() # okay, the var name isn't optimal now :)
for divisor in range(3, int((n+1)**0.5)+1):
if n % divisor == 0:
other = n // divisor
liste.add(divisor)
liste.add(other)
When you found a "low" (<=sqrt(n)
) divisor, other
is just the complementary "high" divisor. This allows the loop to be much shorter.
since you're going to perform intersection by converting to a set
, why not creating a set
in the first place? The advantage is that in a perfect square, you're not adding the same value twice (you could test, but not sure it would be faster, since collisions are rare)
now everything is set
so:
if intersect(di, dj)==[]:
pos+=1
becomes:
if not di.isdisjoint(dj):
pos += 1
set
creation at each iterationset
creation to know if the sets have common values. Just use set.isdisjoint
Last thing: As noted in comments, when testing for primality in the main loop, you're calling is_prime()
again, instead of storing your generated primes in a set
and test with in
instead.
For starters, quit doing a prime factorization. Instead, use Euclid's algorithm to check whether the numerator and denominator have a GCD of 1.
If you do find a need for primes, search for faster generating algorithms, such as the Sieve of Eratosthenes.
Finally, think on this: generate the primes (efficiently); factor each denominator, as you're doing now. However, instead of looping through all possible numerators, how can you generate the valid numerators for your needs?
A few simple optimizations are possible. You could try these and see whether performance improves by enough to work for your use case.
First, the way in which you are finding the primes up to until
could be improved. Rather than checking each number in the range independently against all numbers up to its square root, you could remember primes you've already found so far and only check those up to the square root of the new candidate prime. If a number is non-prime, it is divisible by some prime less than or equal to its square root. Since there are a lot fewer primes less than n
than there are numbers less than n
as n
gets bigger, you can avoid lots of divisor checking by reusing the partial results you have already constructed at each step. This idea is like doing the Sieve or Eratosthenes without writing down all the numbers and crossing them out, but rather just writing down the ones that turn out to be prime as you go.
Second, when you are checking whether the fractions are in lowest terms, you don't need to look for any common divisors; checking for just for prime common divisors will be sufficient. Why? if the fraction is not in lowest terms, there is a common denominator which will be divisible by some prime number. Therefore, we can dispense with getting all factors, and focus on just getting the prime factorization. This can be sped up by only checking the prime numbers we got in the first step as candidate factors and, when we find a match, dividing at each step by the factor we've found. Thus, we can avoid lots of checks here as well. Something like this:
p = 1
while number > 1 do
if number % primes[p] == 0 then
print "Another prime factor is " + primes[p]
number = number / primes[p]
else then p = p + 1
The second of these optimizations will probably give you the most bang for your buck.
Generate the prime factorizations of every possible denominator. From each prime factorization, calculate the totient of the denominator (https://en.wikipedia.org/wiki/Euler%27s_totient_function). That is the number of proper fractions with that denominator. Add up all of those and you're done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With