Alguém pode me explicar uma maneira eficiente de encontrar todos os fatores de um número no Python (2.7)?
Posso criar um algoritmo para fazer isso, mas acho que é mal codificado e leva muito tempo para produzir um resultado para um grande número.
Alguém pode me explicar uma maneira eficiente de encontrar todos os fatores de um número no Python (2.7)?
Posso criar um algoritmo para fazer isso, mas acho que é mal codificado e leva muito tempo para produzir um resultado para um grande número.
primefac
? pypi.python.org/pypi/primefacRespostas:
Isso retornará todos os fatores, muito rapidamente, de um número
n
.Por que raiz quadrada como o limite superior?
sqrt(x) * sqrt(x) = x
. Portanto, se os dois fatores são iguais, ambos são a raiz quadrada. Se você aumentar um fator, precisará diminuir o outro. Isso significa que um dos dois sempre será menor ou igual asqrt(x)
, portanto, você só precisa procurar até esse ponto para encontrar um dos dois fatores correspondentes. Você pode usarx / fac1
para obterfac2
.A
reduce(list.__add__, ...)
é tomar as pequenas listas de[fac1, fac2]
e unindo-os em uma lista longa.O
[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0
retorna um par de fatores se o restante quando você dividirn
pelo menor for zero (ele também não precisa verificar o maior; apenas obtém isso dividindon
pelo menor).O
set(...)
lado de fora está se livrando de duplicatas, o que só acontece com quadrados perfeitos. Poisn = 4
, isso retornará2
duas vezes, paraset
se livrar de um deles.fonte
sqrt
- provavelmente antes de as pessoas realmente pensarem em dar suporte ao Python 3. Acho que o site em que o peguei tentou__iadd__
e foi mais rápido . Parece que me lembro de algo sobrex**0.5
ser mais rápido do quesqrt(x)
em algum momento - e é mais seguro assim.if not n % i
em vez deif n % i == 0
/
retorne um float mesmo que ambos os argumentos sejam inteiros e sejam exatamente divisíveis, ou seja,4 / 2 == 2.0
não2
.from functools import reduce
para fazer esse trabalho.A solução apresentada pelo @agf é ótima, mas é possível obter um tempo de execução ~ 50% mais rápido para um número ímpar arbitrário , verificando a paridade. Como os fatores de um número ímpar sempre são ímpares, não é necessário verificá-los ao lidar com números ímpares.
Acabei de começar a resolver os enigmas do Project Euler . Em alguns problemas, uma verificação do divisor é chamada dentro de dois
for
loops aninhados , e o desempenho dessa função é essencial.Combinando esse fato com a excelente solução da agf, acabei com esta função:
No entanto, em números pequenos (~ <100), a sobrecarga extra dessa alteração pode levar a função a demorar mais.
Fiz alguns testes para verificar a velocidade. Abaixo está o código usado. Para produzir as diferentes parcelas, alterei a mesma
X = range(1,100,1)
.X = intervalo (1.100,1)
Não há diferença significativa aqui, mas com números maiores, a vantagem é óbvia:
X = intervalo (1.100.000.1000) (apenas números ímpares)
X = intervalo (2.100.000.100) (apenas números pares)
X = intervalo (1.100.000.1001) (paridade alternada)
fonte
A resposta da agf é realmente muito legal. Eu queria ver se poderia reescrevê-lo para evitar o uso
reduce()
. Isto é o que eu vim com:Eu também tentei uma versão que usa funções complicadas de gerador:
Eu cronometrei isso computando:
Eu o executei uma vez para permitir que o Python o compilasse, depois executei o comando time (1) três vezes e mantenha o melhor tempo.
Observe que a versão do itertools está construindo uma tupla e transmitindo-a para flatten_iter (). Se eu alterar o código para criar uma lista, ele diminui um pouco:
Acredito que a versão complicada das funções do gerador seja a mais rápida possível no Python. Mas não é realmente muito mais rápido que a versão reduzida, aproximadamente 4% mais rápido com base em minhas medidas.
fonte
for tup in
):factors = lambda n: {f for i in range(1, int(n**0.5)+1) if n % i == 0 for f in [i, n//i]}
Uma abordagem alternativa à resposta da agf:
fonte
reduce()
era significativamente mais rápido, então praticamente fiz tudo, exceto areduce()
parte, da mesma forma que a agf. Para facilitar a leitura, seria bom ver uma chamada de função comois_even(n)
uma expressãon % 2 == 0
.Aqui está uma alternativa à solução da @ agf, que implementa o mesmo algoritmo em um estilo mais pitônico:
Essa solução funciona no Python 2 e no Python 3 sem importações e é muito mais legível. Não testei o desempenho dessa abordagem, mas, assintoticamente, deve ser a mesma e, se o desempenho for uma preocupação séria, nenhuma das soluções será a ideal.
fonte
Existe um algoritmo de força de mercado no SymPy chamado factorint :
Isso levou menos de um minuto. Alterna entre um coquetel de métodos. Consulte a documentação vinculada acima.
Dados todos os fatores primos, todos os outros fatores podem ser construídos facilmente.
Observe que, mesmo que a resposta aceita tenha sido executada por tempo suficiente (ou seja, uma eternidade) para fatorar o número acima, para alguns números grandes ela falhará, como no exemplo a seguir. Isto é devido ao desleixado
int(n**0.5)
. Por exemplo, quandon = 10000000000000079**2
, temosComo 10000000000000079 é primo , o algoritmo da resposta aceita nunca encontrará esse fator. Observe que não é apenas um desdobramento; para números maiores, será desativado por mais. Por esse motivo, é melhor evitar números de ponto flutuante em algoritmos desse tipo.
fonte
sympy.divisors
não seja muito mais rápido, para números com poucos divisores em particular. Tem algum benchmark?sympy.divisors
para 100.000 e mais lenta para algo mais alto (quando a velocidade realmente importa). (E, é claro,sympy.divisors
funciona em números como10000000000000079**2
.)Para n até 10 ** 16 (talvez até um pouco mais), aqui está uma solução rápida e pura do Python 3.6,
fonte
Melhorias adicionais na solução da afg & eryksun. O seguinte trecho de código retorna uma lista classificada de todos os fatores sem alterar a complexidade assintótica do tempo de execução:
Idéia: Em vez de usar a função list.sort () para obter uma lista classificada, que oferece complexidade ao nlog (n); É muito mais rápido usar list.reverse () em l2, o que requer complexidade O (n). (É assim que o python é criado.) Após l2.reverse (), l2 pode ser anexado a l1 para obter a lista classificada de fatores.
Observe que l1 contém i -s que estão aumentando. l2 contém q -s que estão diminuindo. Essa é a razão por trás do uso da idéia acima.
fonte
list.reverse
O (n) não O (1), não que isso mude a complexidade geral.l1 + l2.reversed()
vez de reverter a lista no local.Tentei a maioria dessas respostas maravilhosas com o tempo para comparar sua eficiência com a minha função simples e, no entanto, vejo constantemente as minhas superarem as listadas aqui. Eu pensei em compartilhar e ver o que vocês pensam.
Como está escrito, você precisará importar math para testar, mas substituir math.sqrt (n) por n **. 5 deve funcionar da mesma forma. Eu não me incomodo em perder tempo verificando duplicatas porque duplicatas não podem existir em um conjunto, independentemente.
fonte
xrange(1, int(math.sqrt(n)) + 1)
é avaliado uma vez.Aqui está outra alternativa sem redução que funciona bem com grandes números. Ele usa
sum
para achatar a lista.fonte
sum
oureduce(list.__add__)
para achatar uma lista.Certifique-se de pegar o número maior que o
sqrt(number_to_factor)
de números incomuns como 99, que possui 3 * 3 * 11 efloor sqrt(99)+1 == 10
.fonte
x=8
o esperado:[1, 2, 4, 8]
, tenho:[2, 2, 2]
A maneira mais simples de encontrar fatores de um número:
fonte
Aqui está um exemplo se você deseja usar o número dos números primos para ir muito mais rápido. Essas listas são fáceis de encontrar na internet. Eu adicionei comentários no código.
fonte
um algoritmo potencialmente mais eficiente do que os apresentados aqui já (especialmente se houver pequenos fatos concretos
n
). o truque aqui é ajustar o limite até o qual a divisão de teste é necessária toda vez que fatores primos são encontrados:é claro que isso ainda é uma divisão experimental e nada mais sofisticado. e, portanto, ainda é muito limitado em sua eficiência (especialmente para grandes números sem divisores pequenos).
isso é python3; a divisão
//
deve ser a única coisa que você precisa para se adaptar ao python 2 (adicionarfrom __future__ import division
).fonte
Usar
set(...)
torna o código um pouco mais lento e só é realmente necessário quando você verifica a raiz quadrada. Aqui está a minha versão:o
if sq*sq != num:
condição é necessária para números como 12, em que a raiz quadrada não é um número inteiro, mas o piso da raiz quadrada é um fator.Observe que esta versão não retorna o número em si, mas é uma correção fácil, se você desejar. A saída também não está classificada.
Eu cronometrei a execução 10.000 vezes em todos os números de 1 a 200 e 100 vezes em todos os números de 1 a 5000. Ele supera todas as outras versões que testei, incluindo as soluções de dansalmo, Jason Schorn, oxrock, agf, steveha e eryksun, embora a oxrock seja de longe a mais próxima.
fonte
seu fator máximo não é mais que seu número, então, digamos
voilá!
fonte
fonte
Use algo tão simples quanto a seguinte compreensão da lista, observando que não precisamos testar 1 e o número que estamos tentando encontrar:
Em referência ao uso da raiz quadrada, digamos que queremos encontrar fatores de 10. A parte inteira do
sqrt(10) = 4
portantorange(1, int(sqrt(10))) = [1, 2, 3, 4]
e o teste de até 4 claramente perdem 5.A menos que esteja faltando algo que eu sugiro, se você deve fazer dessa maneira, usando
int(ceil(sqrt(x)))
. É claro que isso produz muitas chamadas desnecessárias para funções.fonte
Eu acho que para melhor legibilidade e velocidade a solução da oxrock é a melhor, então aqui está o código reescrito para o python 3+:
fonte
Fiquei bastante surpreso ao ver a pergunta de que ninguém usava numpy nem quando é muito mais rápido que os loops python. Ao implementar a solução da @ agf com numpy, resultou em média 8 vezes mais rápido . Acredito que se você implementasse algumas das outras soluções numpy, poderia obter momentos incríveis.
Aqui está a minha função:
Observe que os números do eixo x não são a entrada para as funções. A entrada para as funções é 2 para o número no eixo x menos 1. Então, onde dez é a entrada, seria 2 ** 10-1 = 1023
fonte
fonte
Eu acho que essa é a maneira mais simples de fazer isso:
fonte