12

I am trying to find an efficient way to compute Euler's totient function.

What is wrong with this code? It doesn't seem to be working.

def isPrime(a):
    return not ( a < 2 or any(a % i == 0 for i in range(2, int(a ** 0.5) + 1)))

def phi(n):
    y = 1
    for i in range(2,n+1):
        if isPrime(i) is True and n % i  == 0 is True:
            y = y * (1 - 1/i)
        else:
            continue
    return int(y)
Ry-
  • 218,210
  • 55
  • 464
  • 476
Brock West
  • 121
  • 1
  • 1
  • 3

9 Answers9

28

Here's a much faster, working way, based on this description on Wikipedia:

Thus if n is a positive integer, then φ(n) is the number of integers k in the range 1 ≤ k ≤ n for which gcd(n, k) = 1.

I'm not saying this is the fastest or cleanest, but it works.

from math import gcd

def phi(n):
    amount = 0        
    for k in range(1, n + 1):
        if gcd(n, k) == 1:
            amount += 1
    return amount
cedricbellet
  • 148
  • 2
  • 12
orlp
  • 112,504
  • 36
  • 218
  • 315
  • Did you mean range(1, n+1)? – douggard Nov 25 '13 at 16:48
  • 1
    These properties comes handy If p is a prime number, ϕ(p)=p−1 as gcd(p,q)=1where 1<=q

    0, ϕ(p^k)=p^k−p^(k−1) If a and b are relatively prime, ϕ(ab)=ϕ(a).ϕ(b) http://e-maxx-eng.github.io/algebra/phi-function.html

    – Anoop Toffy Apr 09 '16 at 05:12
5

You have three different problems...

  1. y needs to be equal to n as initial value, not 1
  2. As some have mentioned in the comments, don't use integer division
  3. n % i == 0 is True isn't doing what you think because of Python chaining the comparisons! Even if n % i equals 0 then 0 == 0 is True BUT 0 is True is False! Use parens or just get rid of comparing to True since that isn't necessary anyway.

Fixing those problems,

def phi(n):
    y = n
    for i in range(2,n+1):
        if isPrime(i) and n % i == 0:
            y *= 1 - 1.0/i
    return int(y)
Jared
  • 25,627
  • 7
  • 56
  • 61
4

Calculating gcd for every pair in range is not efficient and does not scales. You don't need to iterate throught all the range, if n is not a prime you can check for prime factors up to its square root, refer to https://stackoverflow.com/a/5811176/3393095. We must then update phi for every prime by phi = phi*(1 - 1/prime).

def totatives(n):
    phi = int(n > 1 and n)
    for p in range(2, int(n ** .5) + 1):
        if not n % p:
            phi -= phi // p
            while not n % p:
                n //= p
    #if n is > 1 it means it is prime
    if n > 1: phi -= phi // n 
    return phi
Rodrigo López
  • 4,039
  • 1
  • 19
  • 26
  • 2
    This could be made a lot faster by reworking the loop so it stops at the square root of the current `n` value rather than the initial `n` value. – user2357112 Sep 10 '18 at 17:29
  • 1
    At least it beats the explicit primality tests and gcd computations all the other existing answers use, though. – user2357112 Sep 10 '18 at 17:37
3

I'm working on a cryptographic library in python and this is what i'm using. gcd() is Euclid's method for calculating greatest common divisor, and phi() is the totient function.

def gcd(a, b):
    while b:
        a, b=b, a%b
    return a
def phi(a):
    b=a-1
    c=0
    while b:
        if not gcd(a,b)-1:
            c+=1
        b-=1
    return c
Serinice
  • 118
  • 11
3

Most implementations mentioned by other users rely on calling a gcd() or isPrime() function. In the case you are going to use the phi() function many times, it pays of to calculated these values before hand. A way of doing this is by using a so called sieve algorithm.

https://stackoverflow.com/a/18997575/7217653 This answer on stackoverflow provides us with a fast way of finding all primes below a given number.

Oke, now we can replace isPrime() with a search in our array.

Now the actual phi function:

Wikipedia gives us a clear example: https://en.wikipedia.org/wiki/Euler%27s_totient_function#Example

phi(36) = phi(2^2 * 3^2) = 36 * (1- 1/2) * (1- 1/3) = 30 * 1/2 * 2/3 = 12

In words, this says that the distinct prime factors of 36 are 2 and 3; half of the thirty-six integers from 1 to 36 are divisible by 2, leaving eighteen; a third of those are divisible by 3, leaving twelve numbers that are coprime to 36. And indeed there are twelve positive integers that are coprime with 36 and lower than 36: 1, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, and 35.

TL;DR

With other words: We have to find all the prime factors of our number and then multiply these prime factors together using foreach prime_factor: n *= 1 - 1/prime_factor.

import math

MAX = 10**5

# CREDIT TO https://stackoverflow.com/a/18997575/7217653
def sieve_for_primes_to(n): 
    size = n//2
    sieve = [1]*size
    limit = int(n**0.5)
    for i in range(1,limit):
        if sieve[i]:
            val = 2*i+1
            tmp = ((size-1) - i)//val 
            sieve[i+val::val] = [0]*tmp
    return [2] + [i*2+1 for i, v in enumerate(sieve) if v and i>0]

PRIMES = sieve_for_primes_to(MAX)
print("Primes generated")


def phi(n):
    original_n = n
    prime_factors = []
    prime_index = 0
    while n > 1: # As long as there are more factors to be found
        p = PRIMES[prime_index]
        if (n % p == 0): # is this prime a factor?
            prime_factors.append(p)
            while math.ceil(n / p) == math.floor(n / p): # as long as we can devide our current number by this factor and it gives back a integer remove it
                n = n // p

        prime_index += 1

    for v in prime_factors: # Now we have the prime factors, we do the same calculation as wikipedia
        original_n *= 1 - (1/v)

    return int(original_n)

print(phi(36)) # = phi(2**2 * 3**2) = 36 * (1- 1/2) * (1- 1/3) = 36 * 1/2 * 2/3 = 12
user1235183
  • 3,002
  • 1
  • 27
  • 66
2

It looks like you're trying to use Euler's product formula, but you're not calculating the number of primes which divide a. You're calculating the number of elements relatively prime to a.

In addition, since 1 and i are both integers, so is the division, in this case you always get 0.

Joel
  • 5,618
  • 1
  • 20
  • 19
2

With regards to efficiency, I haven't noticed anyone mention that gcd(k,n)=gcd(n-k,n). Using this fact can save roughly half the work needed for the methods involving the use of the gcd. Just start the count with 2 (because 1/n and (n-1)/k will always be irreducible) and add 2 each time the gcd is one.

Bill Bell
  • 21,021
  • 5
  • 43
  • 58
  • You're not considering the work required to calculate the work required to compare `n` with `2*k`, as well as the work required to subtract `n-k`. – msinghal Jul 16 '15 at 08:37
2

Here is a shorter implementation of orlp's answer.

from math import gcd
def phi(n): return sum([gcd(n, k)==1 for k in range(1, n+1)])

As others have already mentioned it leaves room for performance optimization.

Udo Klein
  • 6,784
  • 1
  • 36
  • 61
1

Actually to calculate phi(any number say n)
We use the Formula
where p are the prime factors of n.

So, you have few mistakes in your code:
1.y should be equal to n
2. For 1/i actually 1 and i both are integers so their evaluation will also be an integer,thus it will lead to wrong results.

Here is the code with required corrections.

def phi(n):
    y = n
    for i in range(2,n+1):
        if isPrime(i) and n % i  == 0 :
            y -= y/i
        else:
            continue
    return int(y)
alphaguy
  • 540
  • 3
  • 17