Ho una lista Python dei fattori primi di un numero. Come faccio (pythonically) Trova tutti i fattori?

StackOverflow https://stackoverflow.com/questions/3643725

  •  30-09-2019
  •  | 
  •  

Domanda

Sto lavorando su un problema Project Euler che richiede fattorizzazione di un numero intero. Posso venire con un elenco di tutti i numeri primi che sono il fattore di un dato numero. Il Teorema Fondamentale dell'Aritmetica implica che posso utilizzare questo elenco per ricavare tutti fattore del numero.

Il mio piano attuale è quello di prendere ogni numero nell'elenco dei numeri primi di base e aumentare il suo potere fino a quando non è più un fattore intero di trovare i massimi esponenti per ogni serata. Poi, io moltiplicherò ogni possibile combinazione di coppie di prime-esponente.

Così, per esempio, per la 180:

Given: prime factors of 180: [2, 3, 5]
Find maximum exponent of each  factor: 
    180 / 2^1 = 90
    180 / 2^2 = 45
    180 / 2^3 = 22.5 - not an integer, so 2 is the maximum exponent of 2.

    180 / 3^1 = 60
    180 / 3^2 = 20
    180 / 3^3 = 6.6 - not an integer, so 2 is the maximum exponent of 3.

    180 / 5^1 = 36
    180 / 5^2 = 7.2 - not an integer, so 1 is the maximum exponent of 5.

Avanti, fare ogni combinazione di questi fino al massimo esponente per ottenere i fattori:

    2^0 * 3^0 * 5^0 = 1
    2^1 * 3^0 * 5^0 = 2
    2^2 * 3^0 * 5^0 = 4
    2^0 * 3^1 * 5^0 = 3
    2^1 * 3^1 * 5^0 = 6
    2^2 * 3^1 * 5^0 = 12
    2^0 * 3^2 * 5^0 = 9
    2^1 * 3^2 * 5^0 = 18
    2^2 * 3^2 * 5^0 = 36
    2^0 * 3^0 * 5^1 = 5
    2^1 * 3^0 * 5^1 = 10
    2^2 * 3^0 * 5^1 = 20
    2^0 * 3^1 * 5^1 = 15
    2^1 * 3^1 * 5^1 = 30
    2^2 * 3^1 * 5^1 = 60
    2^0 * 3^2 * 5^1 = 45
    2^1 * 3^2 * 5^1 = 90
    2^2 * 3^2 * 5^1 = 180

Così la lista di fattori = [1, 2, 3, 4, 5, 6, 9, 10, 12, 15, 18, 20, 30, 36, 45, 60, 90, 180]

Ecco il codice che ho finora. Due problemi: in primo luogo, non credo che sia molto Pythonic a tutti. Mi piacerebbe sistemare le cose. In secondo luogo, davvero non hanno un modo Pythonic per fare il secondo passo combinatoria. Per la vergogna, ti ho risparmiato dal set ridicola di cicli.

n è il numero che vogliamo fattore. listOfAllPrimes è una lista precalcolato dei numeri primi fino a 10 milioni di euro.

def getListOfFactors(n, listOfAllPrimes):
    maxFactor = int(math.sqrt(n)) + 1
    eligiblePrimes = filter(lambda x: x <= maxFactor, listOfAllPrimes)
    listOfBasePrimes = filter(lambda x: n % x ==0, eligiblePrimes)

    listOfExponents = [] #(do I have to do this?)
    for x in listOfBasePrimes:
        y = 1
        while (x**(y+1)) % n == 0:
            y += 1
        listOfExponents.append(y)
È stato utile?

Soluzione

Al posto di un elenco di esponenti, considera semplicemente ripetere ogni fattore primo per il numero di volte che è un fattore. Dopo di che, lavorando sul primefactors risultante lista-con-ripetizioni, itertools.combinations fa proprio quello che serve - si richiedono solo le combinazioni di lunghezza 2 alle voci len(primefactors) - 1 inclusi (le combinazioni di uno solo sono i fattori primi, che di tutti loro sarà il numero originale - se volete quelli troppo, basta usare range(1, len(primefactors) + 1) al posto del range(2, len(primefactors)) che il mio suggerimento principale sarebbe usare).

Ci saranno ripetizioni nei risultati (ad esempio, appariranno 6 due volte come fattore di 12, poiché primefactors di quest'ultimo sarà [2, 2, 3]) e possono ovviamente essere eliminati nei modi usuali (cioè sorted(set(results)) per esempio).

Per calcolare primefactors dato listOfAllPrimes, si consideri ad esempio:

def getprimefactors(n):
    primefactors = []
    primeind = 0
    p = listOfAllPrimes[primeind]
    while p <= n:
        if n % p == 0:
            primefactors.append(p)
            n //= p
        else:
            primeind += 1
            p = listOfAllPrimes[primeind]
    return primefactors

Altri suggerimenti

Perché si inizia la soluzione dal set di fattori primi? quando di fattorizzare un numero si può facilmente ottenere tutti i suoi fattori primi (ripetuta) e da loro esponenti per ogni fattore. Con questo in mente, è possibile scrivere questo:

import itertools
prime_factors = get_prime_factors(180) 
# 2, 2, 3, 3, 5 
factorization = [(f, len(list(fs))) for (f, fs) in itertools.groupby(prime_factors)]
# [(2, 2), (3, 2), (5, 1)]
values = [[(factor**e) for e in range(exp+1)] for (factor, exp) in factorization]
# [[1, 2, 4], [1, 3, 9], [1, 5]]
print sorted(product(xs) for xs in itertools.product(*values))
# [1, 2, 3, 4, 5, 6, 9, 10, 12, 15, 18, 20, 30, 36, 45, 60, 90, 180]

get_prime_factors e product non sono implementati qui, ma si ottiene l'idea (entrambi sono piuttosto semplice da scrivere)

IMHO, essendo i problemi matematici, i problemi di Eulero può essere ben risolto utilizzando funzionale invece di stile imperativo (anche se riconosco che alcune soluzioni non possono venire fuori come divinatorio, se lo desideri).

Si potrebbe usare itertools.combinations() per ottenere tutte le possibili combinazioni di fattori una volta che hai ottenuto la tua lista dei ripetuti-numeri primi, come [2, 2, 3, 3, 5] per 180. Quindi, è sufficiente moltiplicare i componenti di ogni combinazione si arriva un fattore.

Con pochi cooler Python caratteristiche:

def factors( num ):
    for p in primes:
        if num==1: break # stop when num is 1
        while True: # these factors may be repeated 
            new, rest = divmod(num, p) # does div and mod at once
            if rest==0: # its divisible
                yield p # current prime is factor
                num = new # continue with the div'd number
            else:
                break # no factor so break from the while


print list(factors(2*2*3*3*5*7*11*11*13)) # [2, 2, 3, 3, 5, 7, 11, 11, 13] ofc

Ecco un semplice ed efficace soluzione al problema originale:

def getDivisors(n):
    divisors = []
    d = 1
    while d*d < n:
        if n % d == 0:
            divisors.append(d)
            divisors.append(n / d);
        d += 1
    if d*d == n:
        divisors.append(d)
    return divisors

La lista di uscita è non differenziati. È possibile rendere più "divinatorio", se si vuole, qualunque cosa significhi.

Un tutto in un'unica soluzione; vale a dire senza bisogno di un elenco esistente dei fattori primi.

#!/usr/bin/python3 -O

from primegen import erat3 as generate_primes # see Note[1]
import operator as op, functools as ft, itertools as it

def all_factors(number):
    prime_powers= []

    for prime in generate_primes(): # for prime in listOfAllPrimes
        if prime > number: break

        this_prime_powers= [1]
        new_number, modulo= divmod(number, prime)

        while not modulo:
            number= new_number
            this_prime_powers.append(this_prime_powers[-1] * prime)
            new_number, modulo= divmod(number, prime)

        if len(this_prime_powers) > 1:
            prime_powers.append(this_prime_powers)

    # at this point:
    # if number was 360, prime_powers is [[1, 2, 4, 8], [1, 3, 9], [1, 5]]
    # if number was 210, prime_powers is [[1, 2], [1, 3], [1, 5], [1, 7]]

    return sorted(
        ft.reduce(op.mul, combination, 1)
        for combination in it.product(*prime_powers))

if __name__ == "__main__":
    def num_result(number):
        return number, all_factors(number)
    print(num_result(360))
    print(num_result(210))
    print(num_result(7))

Nota [1] : Come un generatore di numeri primi, è possibile scegliere uno da Come implementare un efficiente infinita generatore di numeri primi in Python? o utilizzare il proprio (ad esempio il listOfAllPrimes).

Questo produce un elenco completo fattore, cioè compresa 1 e l'argomento number stessa. Se si vuole omettere questi, è possibile utilizzare all_factors(number)[1:-1].

$ allfactors.py 
(360, [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18, 20, 24, 30, 36, 40, 45, 60, 72, 90, 120, 180, 360])
(210, [1, 2, 3, 5, 6, 7, 10, 14, 15, 21, 30, 35, 42, 70, 105, 210])
(7, [1, 7])
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top