From cfba98d2e0b657758748afc251c9624c16c71e5c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 1 Jul 2025 21:08:39 +1000 Subject: [PATCH] Improved eulertotient(n) implementation --- imp/math/primes.py | 96 ++++++++++++++-------------------------------- imp/math/util.py | 3 +- 2 files changed, 31 insertions(+), 68 deletions(-) diff --git a/imp/math/primes.py b/imp/math/primes.py index eaeebcb..b130a0c 100644 --- a/imp/math/primes.py +++ b/imp/math/primes.py @@ -1,5 +1,10 @@ -from math import gcd -from imp.math.numbers import bigomega +from math import gcd, inf + +from imp.math.numbers import bigomega, factors +from imp.extern.primefac import ( + isprime, + primegen as Primes, +) def coprime(n: int, m: int) -> bool: return gcd(n, m) == 1 @@ -18,68 +23,25 @@ def semiprime(n: int) -> bool: ''' return almostprime(n, 2) -''' -Euler's Totient (Phi) Function -''' -def totient(n: int) -> int: - 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 - -''' -Tests the primality of an integer using its totient. -NOTE: If totient(n) has already been calculated - then pass it as the optional phi parameter. -''' -def is_prime(n: int, phi: int = None) -> bool: - return n - 1 == (phi if phi is not None else totient(n)) - -''' -Prime number generator function. -Returns the tuple (p, phi(p)) where p is prime - and phi is Euler's totient function. -''' -def prime_gen(yield_phi: bool = False) -> int | tuple[int, int]: - n = 1 - while True: - n += 1 - phi = totient(n) - if is_prime(n, phi=phi): - if yield_phi: - yield (n, phi) - else: - yield n - -''' -Returns the prime factorisation of a number. -Returns a list of tuples (p, m) where p is -a prime factor and m is its multiplicity. -NOTE: uses a trial division algorithm -''' -def prime_factors(n: int) -> list[tuple[int, int]]: - phi = totient(n) - if is_prime(n, phi=phi): - return [(n, 1)] - factors = [] - for p in prime_gen(yield_phi=False): - if p >= n: - break - # check if divisor - multiplicity = 0 - while n % p == 0: - n //= p - multiplicity += 1 - if multiplicity: - factors.append((p, multiplicity)) - if is_prime(n): - break - if n != 1: - factors.append((n, 1)) - return factors - +def eulertotient(x: int | list) -> int: + ''' + Evaluates Euler's Totient function. + Input: `x: int` is prime factorised by Lucas A. Brown's primefac.py + else `x: list` is assumed to the prime factorisation of `x: int` + ''' + pfactors = x if isinstance(x, list) else factors(n) + return prod((p-1)*(p**(e-1)) for (p, e) in pfactors) +# def eulertotient(n: int) -> int: +# ''' +# Uses trial division to compute +# Euler's Totient (Phi) Function. +# ''' +# 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 diff --git a/imp/math/util.py b/imp/math/util.py index 69631c6..4dacda2 100644 --- a/imp/math/util.py +++ b/imp/math/util.py @@ -1,8 +1,9 @@ +from collections.abc import Iterable from itertools import chain, combinations def digits(n: int) -> int: return len(str(n)) -def powerset(iterable): +def powerset(iterable: Iterable) -> Iterable: s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))