algorithm math

algorithm - ¿Cuántos números debajo de N son coprimes a N?



math (4)

En breve:

Dado que a es coprime a b si GCD (a, b) = 1 (donde GCD significa gran divisor común ), ¿cuántos enteros positivos debajo de N son coprime a N?

¿Hay alguna manera inteligente?

Cosas no necesarias

Aquí está la manera más tonta:

def count_coprime(N): counter = 0 for n in xrange(1,N): if gcd(n,N) == 1: counter += 1 return counter

Funciona, pero es lento, y tonto. Me gustaría usar un algoritmo inteligente y más rápido. Traté de usar factores primos y divisores de N, pero siempre obtengo algo que no funciona con N. más grande.

Creo que el algoritmo debería poder contarlos sin calcularlos todos, como hace el algoritmo más tonto: P

Editar

Parece que he encontrado un trabajo:

def a_bit_more_clever_counter(N): result = N - 1 factors = [] for factor, multiplicity in factorGenerator(N): result -= N/factor - 1 for pf in factors: if lcm(pf, factor) < N: result += N/lcm(pf, factor) - 1 factors += [factor] return result

donde mcm es el mínimo común múltiplo. ¿Alguien tiene uno mejor?

Nota

Estoy usando python, creo que el código debería ser legible incluso para quien no conoce python, si encuentra algo que no esté claro, simplemente pregunte en los comentarios. Me interesa el algoritmo y las matemáticas, la idea.



Aquí hay una implementación simple y directa de la fórmula dada en la página de wikipedia, usando gmpy para una fácil factorización (estoy sesgado, pero probablemente quieras gmpy si te importa jugar con cosas enteras divertidas en Python ... ;-):

import gmpy def prime_factors(x): prime = gmpy.mpz(2) x = gmpy.mpz(x) factors = {} while x >= prime: newx, mult = x.remove(prime) if mult: factors[prime] = mult x = newx prime = prime.next_prime() return factors def euler_phi(x): fac = prime_factors(x) result = 1 for factor in fac: result *= (factor-1) * (factor**(fac[factor]-1)) return result

Por ejemplo, en mi modesta estación de trabajo, computar euler_phi (123456789) [para lo cual recibo 82260072] toma 937 microsegundos (con Python 2.5; 897 con 2.4), lo que parece ser un rendimiento bastante razonable.


Esta es la función totient de Euler , phi.

Tiene la emocionante propiedad de ser multiplicativo: si gcd (m, n) = 1, entonces phi (mn) = phi (m) phi (n). Y phi es fácil de calcular para potencias de primos, ya que todo lo que está debajo de ellos es coprime excepto los múltiplos de potencias más pequeñas del mismo primo.

Obviamente, la factorización aún no es un problema trivial, pero incluso las divisiones de prueba sqrt (n) (suficientes para encontrar todos los factores primos) superan las aplicaciones n-1 del algoritmo de Euclid.

Si realiza una memorización, puede reducir el costo promedio de computar una gran cantidad de ellos.


[Editar] Un último pensamiento, que (IMO) es lo suficientemente importante para que lo plantee al principio: si está reuniendo un montón de pacientes a la vez, puede evitar una gran cantidad de trabajo redundante. No se moleste en comenzar con números grandes para encontrar sus factores más pequeños; en su lugar, repita los factores más pequeños y acumule resultados para los números más grandes.

class Totient: def __init__(self, n): self.totients = [1 for i in range(n)] for i in range(2, n): if self.totients[i] == 1: for j in range(i, n, i): self.totients[j] *= i - 1 k = j / i while k % i == 0: self.totients[j] *= i k /= i def __call__(self, i): return self.totients[i] if __name__ == ''__main__'': from itertools import imap totient = Totient(10000) print sum(imap(totient, range(10000)))

Esto toma solo 8ms en mi escritorio.

La página de Wikipedia sobre la función principal de Euler tiene algunos buenos resultados matemáticos.

cuenta los números de coprime y más pequeños que cada divisor de : esto tiene un mapeo trivial * para contar los enteros de a , entonces la suma total es .

* por la segunda definicion de trivial

Esto es perfecto para una aplicación de la fórmula de inversión de Möbius , un truco inteligente para invertir sumas de esta forma exacta.

Esto conduce naturalmente al código.

def totient(n): if n == 1: return 1 return sum(d * mobius(n / d) for d in range(1, n+1) if n % d == 0) def mobius(n): result, i = 1, 2 while n >= i: if n % i == 0: n = n / i if n % i == 0: return 0 result = -result i = i + 1 return result

Existen mejores implementaciones de la función de Möbius , y podría ser memorizada para la velocidad, pero esto debería ser bastante fácil de seguir.

El cálculo más obvio de la función totiente es

En otras palabras, factorice completamente el número en primos y exponentes únicos, y haga una simple multiplicación a partir de ahí.

from operator import mul def totient(n): return int(reduce(mul, (1 - 1.0 / p for p in prime_factors(n)), n)) def primes_factors(n): i = 2 while n >= i: if n % i == 0: yield i n = n / i while n % i == 0: n = n / i i = i + 1

Nuevamente, existen mejores implementaciones de prime_factors , pero esto está hecho para una fácil lectura.

# helper functions

from collections import defaultdict from itertools import count from operator import mul def gcd(a, b): while a != 0: a, b = b % a, a return b def lcm(a, b): return a * b / gcd(a, b) primes_cache, prime_jumps = [], defaultdict(list) def primes(): prime = 1 for i in count(): if i < len(primes_cache): prime = primes_cache[i] else: prime += 1 while prime in prime_jumps: for skip in prime_jumps[prime]: prime_jumps[prime + skip] += [skip] del prime_jumps[prime] prime += 1 prime_jumps[prime + prime] += [prime] primes_cache.append(prime) yield prime def factorize(n): for prime in primes(): if prime > n: return exponent = 0 while n % prime == 0: exponent, n = exponent + 1, n / prime if exponent != 0: yield prime, exponent

# OP''s first attempt

def totient1(n): counter = 0 for i in xrange(1, n): if gcd(i, n) == 1: counter += 1 return counter

# OP''s second attempt

# I don''t understand the algorithm, and just copying it yields inaccurate results

# Möbius inversion

def totient2(n): if n == 1: return 1 return sum(d * mobius(n / d) for d in xrange(1, n+1) if n % d == 0) mobius_cache = {} def mobius(n): result, stack = 1, [n] for prime in primes(): if n in mobius_cache: result = mobius_cache[n] break if n % prime == 0: n /= prime if n % prime == 0: result = 0 break stack.append(n) if prime > n: break for n in stack[::-1]: mobius_cache[n] = result result = -result return -result

# traditional formula

def totient3(n): return int(reduce(mul, (1 - 1.0 / p for p, exp in factorize(n)), n))

# traditional formula, no division

def totient4(n): return reduce(mul, ((p-1) * p ** (exp-1) for p, exp in factorize(n)), 1)

Usando este código para calcular los totients de todos los números del 1 al 9999 en mi escritorio, con un promedio de más de 5 carreras,

  • totient1 toma para siempre
  • totient2 toma 10s
  • totient3 toma 1.3s
  • totient4 toma 1.3s