Qual é a melhor maneira de obter todos os divisores de um número?

105

Esta é a maneira muito estúpida:

def divisorGenerator(n):
    for i in xrange(1,n/2+1):
        if n%i == 0: yield i
    yield n

O resultado que gostaria de obter é semelhante a este, mas gostaria de um algoritmo mais inteligente (este é muito lento e burro :-)

Posso encontrar os fatores primos e sua multiplicidade com rapidez suficiente. Tenho um gerador que gera fator desta forma:

(fator1, multiplicidade1)
(fator2, multiplicidade2)
(fator3, multiplicidade3)
e assim por diante ...

ou seja, a saída de

for i in factorGenerator(100):
    print i

é:

(2, 2)
(5, 2)

Não sei o quanto isso é útil para o que quero fazer (codifiquei para outros problemas), de qualquer forma, gostaria de uma maneira mais inteligente de fazer

for i in divisorGen(100):
    print i

envie este:

1
2
4
5
10
20
25
50
100

ATUALIZAÇÃO: Muito obrigado a Greg Hewgill e seu "jeito inteligente" :) Calculando todos os divisores de 100000000 levou 0,01s com seu caminho contra os 39s que o modo idiota pegou na minha máquina, muito legal: D

ATUALIZAÇÃO 2: Pare de dizer que esta é uma duplicata desta postagem. Calcular o número do divisor de um determinado número não precisa calcular todos os divisores. É um problema diferente, se você acha que não é, então procure por "Função Divisor" na wikipedia. Leia as perguntas e a resposta antes de postar, se você não entende qual é o assunto, simplesmente não acrescente respostas inúteis e já dadas.

Andrea Ambu
fonte
A razão pela qual foi sugerido que esta questão era quase uma duplicata do "Algoritmo para calcular o número de divisores de um determinado número" foi que a primeira etapa sugerida nessa questão foi encontrar todos os divisores , o que eu acredito ser exatamente o que você estava tentando fazer?
Andrew Edgecombe,
4
Andrew, a fim de descobrir quantos divisores existem, você simplesmente precisa encontrar os fatores primos e então usá-los para contar quantos divisores podem existir. Encontrar divisores não é necessário nesse caso.
Loïc Faure-Lacroix
1
@Andrea Ambu, corrija os nomes das funções
minerais

Respostas:

77

Dada a sua factorGeneratorfunção, aqui está um divisorGenque deve funcionar:

def divisorGen(n):
    factors = list(factorGenerator(n))
    nfactors = len(factors)
    f = [0] * nfactors
    while True:
        yield reduce(lambda x, y: x*y, [factors[x][0]**f[x] for x in range(nfactors)], 1)
        i = 0
        while True:
            f[i] += 1
            if f[i] <= factors[i][1]:
                break
            f[i] = 0
            i += 1
            if i >= nfactors:
                return

A eficiência geral desse algoritmo dependerá inteiramente da eficiência do factorGenerator.

Greg Hewgill
fonte
2
uau demorou 0,01 para calcular todos os divisores de 100000000 contra os 39s que pegaram o jeito idiota (parando em n / 2) muito legal, obrigado!
Andrea Ambu,
47
Para aqueles de nós que não entendem Pythonese, o que isso realmente está fazendo?
Matthew Scharley,
1
monóxido: calcula todas as combinações multiplicativas dos fatores dados. A maior parte deve ser autoexplicativa; a linha de "rendimento" é como um retorno, mas continua após retornar um valor. [0] * nfactors cria uma lista de zeros de nfactors de comprimento. reduz (...) calcula o produto dos fatores.
Greg Hewgill,
A notação de redução e lambda eram as partes que realmente me confundiam. Tentei implementar um algoritmo para fazer isso em C # usando uma função recursiva para percorrer a matriz de fatores e multiplicá-los todos juntos, mas parece ter um desempenho horrível em números como 1024 que têm muitos fatores
Matthew Scharley,
3
É claro que isso é dramaticamente melhor do que dividir por todos os números até n / 2 ou mesmo sqrt (n), mas esta implementação em particular tem duas desvantagens: bastante ineficaz: toneladas de multiplicação e exponenciação, multiplicação repetida dos mesmos poderes etc. Parece pitônico, mas não acho que o Python tenha como objetivo matar o desempenho. Problema dois: os divisores não são devolvidos em ordem.
Tomasz Gandor
34

Para expandir o que Shimi disse, você deve executar seu loop apenas de 1 até a raiz quadrada de n. Então, para encontrar o par, faça n / i, e isso cobrirá todo o espaço do problema.

Como também foi observado, este é um problema NP, ou 'difícil'. A busca exaustiva, da maneira como você a está fazendo, é tão boa quanto possível para respostas garantidas. Esse fato é usado por algoritmos de criptografia e similares para ajudar a protegê-los. Se alguém resolvesse este problema, a maior parte, senão toda a nossa comunicação 'segura' atual, se tornaria insegura.

Código Python:

import math

def divisorGenerator(n):
    large_divisors = []
    for i in xrange(1, int(math.sqrt(n) + 1)):
        if n % i == 0:
            yield i
            if i*i != n:
                large_divisors.append(n / i)
    for divisor in reversed(large_divisors):
        yield divisor

print list(divisorGenerator(100))

Que deve gerar uma lista como:

[1, 2, 4, 5, 10, 20, 25, 50, 100]
Matthew Scharley
fonte
2
Porque, uma vez que você tem uma lista de elementos entre 1..10, você pode gerar qualquer um dos elementos entre 11..100 trivialmente. Você obtém {1, 2, 4, 5, 10}. Divida 100 por cada um desses elementos e você {100, 50, 20, 25, 10}.
Matthew Scharley,
2
Os fatores são SEMPRE gerados aos pares, em virtude da definição. Ao pesquisar apenas para sqrt (n), você está reduzindo seu trabalho em 2 pontos.
Matthew Scharley
É muito mais rápido do que a versão em meu post, mas ainda é muito lento do que a versão usando fatores primos
Andrea Ambu
Concordo que essa não é a melhor solução. Eu estava simplesmente apontando uma maneira 'melhor' de fazer a pesquisa 'burra' que já economizaria muito tempo.
Matthew Scharley,
A fatoração não mostrou ser NP-difícil. en.wikipedia.org/wiki/Integer_factorization E o problema era encontrar todos os divisores, dado que os fatores primos (a parte difícil) já haviam sido encontrados.
Jamie,
19

Embora já existam muitas soluções para isso, eu realmente tenho que postar isso :)

Esta está:

  • legível
  • curto
  • independente, pronto para copiar e colar
  • rápido (em casos com muitos fatores primos e divisores,> 10 vezes mais rápido do que a solução aceita)
  • compatível com python3, python2 e pypy

Código:

def divisors(n):
    # get factors and their counts
    factors = {}
    nn = n
    i = 2
    while i*i <= nn:
        while nn % i == 0:
            factors[i] = factors.get(i, 0) + 1
            nn //= i
        i += 1
    if nn > 1:
        factors[nn] = factors.get(nn, 0) + 1

    primes = list(factors.keys())

    # generates factors from primes[k:] subset
    def generate(k):
        if k == len(primes):
            yield 1
        else:
            rest = generate(k+1)
            prime = primes[k]
            for factor in rest:
                prime_to_i = 1
                # prime_to_i iterates prime**i values, i being all possible exponents
                for _ in range(factors[prime] + 1):
                    yield factor * prime_to_i
                    prime_to_i *= prime

    # in python3, `yield from generate(0)` would also work
    for factor in generate(0):
        yield factor
Tomas Kulich
fonte
Eu substituiria while i*i <= nnpor while i <= limit, ondelimit = math.sqrt(n)
Rafa0809
17

Eu acho que você pode parar em math.sqrt(n) vez de n / 2.

Vou dar um exemplo para que você possa entender facilmente. Agora o sqrt(28)é 5.29por isso ceil(5.29)será 6. Então, eu se eu vou parar em 6 então eu pode obter todos os divisores. Quão?

Primeiro veja o código e depois a imagem:

import math
def divisors(n):
    divs = [1]
    for i in xrange(2,int(math.sqrt(n))+1):
        if n%i == 0:
            divs.extend([i,n/i])
    divs.extend([n])
    return list(set(divs))

Agora, veja a imagem abaixo:

Digamos que eu já tenha adicionado 1à minha lista de divisores e comece com i=2isso

Divisores de 28

Portanto, no final de todas as iterações, conforme adicionei o quociente e o divisor à minha lista, todos os divisores de 28 são preenchidos.

Fonte: Como determinar os divisores de um número

Anivarth
fonte
2
Legal legal!! math.sqrt(n) instead of n/2é obrigatório para a elegância
Rafa0809,
Isso está incorreto. Você esqueceu que n é divisível por si só.
jasonleonhard de
1
Boa resposta. Simples e claro. Mas para o python 3, há 2 alterações necessárias: n / i deve ser digitado usando int (n / i) porque n / i produz um número flutuante. Além disso, o rangex está obsoleto no python 3 e foi substituído por range.
Geoffroy CALA
7

Gosto da solução de Greg, mas gostaria que fosse mais parecida com python. Acho que seria mais rápido e legível; então, depois de algum tempo codificando, descobri isso.

As duas primeiras funções são necessárias para fazer o produto cartesiano das listas. E pode ser reutilizado sempre que houver esse problema. A propósito, tive que programar sozinho, se alguém souber de uma solução padrão para esse problema, sinta-se à vontade para entrar em contato comigo.

"Factorgenerator" agora retorna um dicionário. E então o dicionário é alimentado em "divisores", que o usa para gerar primeiro uma lista de listas, onde cada lista é a lista dos fatores da forma p ^ n com p '. Em seguida, fazemos o produto cartesiano dessas listas e, finalmente, usamos a solução de Greg para gerar o divisor. Nós os classificamos e os devolvemos.

Eu testei e parece ser um pouco mais rápido que a versão anterior. Eu testei como parte de um programa maior, então não posso dizer o quanto é mais rápido.

Pietro Speroni (pietrosperoni dot it)

from math import sqrt


##############################################################
### cartesian product of lists ##################################
##############################################################

def appendEs2Sequences(sequences,es):
    result=[]
    if not sequences:
        for e in es:
            result.append([e])
    else:
        for e in es:
            result+=[seq+[e] for seq in sequences]
    return result


def cartesianproduct(lists):
    """
    given a list of lists,
    returns all the possible combinations taking one element from each list
    The list does not have to be of equal length
    """
    return reduce(appendEs2Sequences,lists,[])

##############################################################
### prime factors of a natural ##################################
##############################################################

def primefactors(n):
    '''lists prime factors, from greatest to smallest'''  
    i = 2
    while i<=sqrt(n):
        if n%i==0:
            l = primefactors(n/i)
            l.append(i)
            return l
        i+=1
    return [n]      # n is prime


##############################################################
### factorization of a natural ##################################
##############################################################

def factorGenerator(n):
    p = primefactors(n)
    factors={}
    for p1 in p:
        try:
            factors[p1]+=1
        except KeyError:
            factors[p1]=1
    return factors

def divisors(n):
    factors = factorGenerator(n)
    divisors=[]
    listexponents=[map(lambda x:k**x,range(0,factors[k]+1)) for k in factors.keys()]
    listfactors=cartesianproduct(listexponents)
    for f in listfactors:
        divisors.append(reduce(lambda x, y: x*y, f, 1))
    divisors.sort()
    return divisors



print divisors(60668796879)

PS, é a primeira vez que estou postando no stackoverflow. Estou ansioso por qualquer feedback.

Pietro Speroni
fonte
No Python 2.6, existe um itertools.product ().
jfs
Uma versão que usa geradores em vez de list.append em todos os lugares pode ser mais limpa.
jfs
A peneira de Eratóstenes pode ser usada para gerar números primos menores ou iguais a sqrt (n) stackoverflow.com/questions/188425/project-euler-problem#193605
jfs
1
Estilo de codificação: expoentes = [k ** x para k, v em fatores.items () para x no intervalo (v + 1)]
jfs
Para listexponents: [[k ** x para x no intervalo (v + 1)] para k, v em factor.items ()]
klenwell
3

Esta é uma maneira inteligente e rápida de fazer isso para números até e cerca de 10 ** 16 no Python 3.6 puro,

from itertools import compress

def primes(n):
    """ Returns  a list of primes < n for n > 2 """
    sieve = bytearray([True]) * (n//2)
    for i in range(3,int(n**0.5)+1,2):
        if sieve[i//2]:
            sieve[i*i//2::i] = bytearray((n-i*i-1)//(2*i)+1)
    return [2,*compress(range(3,n,2), sieve[1:])]

def factorization(n):
    """ Returns a list of the prime factorization of n """
    pf = []
    for p in primeslist:
      if p*p > n : break
      count = 0
      while not n % p:
        n //= p
        count += 1
      if count > 0: pf.append((p, count))
    if n > 1: pf.append((n, 1))
    return pf

def divisors(n):
    """ Returns an unsorted list of the divisors of n """
    divs = [1]
    for p, e in factorization(n):
        divs += [x*p**k for k in range(1,e+1) for x in divs]
    return divs

n = 600851475143
primeslist = primes(int(n**0.5)+1) 
print(divisors(n))
Bruno Astrolino
fonte
Qual é o nome do algoritmo usado para encontrar os primos e fatorar? Porque quero implementar isso em C # ..
Kyu96
2

Adaptado do CodeReview , aqui está uma variante que funciona com num=1!

from itertools import product
import operator

def prod(ls):
   return reduce(operator.mul, ls, 1)

def powered(factors, powers):
   return prod(f**p for (f,p) in zip(factors, powers))


def divisors(num) :

   pf = dict(prime_factors(num))
   primes = pf.keys()
   #For each prime, possible exponents
   exponents = [range(i+1) for i in pf.values()]
   return (powered(primes,es) for es in product(*exponents))
YvesgereY
fonte
1
Parece que estou recebendo um erro: NameError: global name 'prime_factors' is not defined. Nenhuma das outras respostas, nem a pergunta original, define o que isso faz.
AnnanFay
2

Vou apenas adicionar uma versão ligeiramente revisada do Anivarth (como acredito que seja a mais pítônica) para referência futura.

from math import sqrt

def divisors(n):
    divs = {1,n}
    for i in range(2,int(sqrt(n))+1):
        if n%i == 0:
            divs.update((i,n//i))
    return divs
ppw0
fonte
1

Velha pergunta, mas aqui está minha opinião:

def divs(n, m):
    if m == 1: return [1]
    if n % m == 0: return [m] + divs(n, m - 1)
    return divs(n, m - 1)

Você pode fazer proxy com:

def divisorGenerator(n):
    for x in reversed(divs(n, n)):
        yield x

NOTA: Para idiomas que suportam, isso pode ser recursivo.

Joksnet
fonte
0

Supondo que a factorsfunção retorne os fatores de n (por exemplo, factors(60)retorna a lista [2, 2, 3, 5]), aqui está uma função para calcular os divisores de n :

function divisors(n)
    divs := [1]
    for fact in factors(n)
        temp := []
        for div in divs
            if fact * div not in divs
                append fact * div to temp
        divs := divs + temp
    return divs
user448810
fonte
Isso é python? De qualquer forma, não é python 3.x com certeza.
GinKin de
É pseudocódigo, que deve ser simples de traduzir para python.
user448810
3 anos de atraso, antes tarde do que nunca :) IMO, este é o código mais simples e curto para fazer isso. Não tenho uma tabela de comparação, mas posso fatorar e calcular divisores de até um milhão em 1s em meu laptop portátil i5.
Riyaz Mansoor
0

Aqui está minha solução. Parece ser burro, mas funciona bem ... e eu estava tentando encontrar todos os divisores adequados, então o loop começou em i = 2.

import math as m 

def findfac(n):
    faclist = [1]
    for i in range(2, int(m.sqrt(n) + 2)):
        if n%i == 0:
            if i not in faclist:
                faclist.append(i)
                if n/i not in faclist:
                    faclist.append(n/i)
    return facts
Amber Xue
fonte
erro de digitação: retornar fatos => retornar lista de fatos
Jonath P
0

Se você só se preocupa em usar as compreensões de lista e nada mais importa para você!

from itertools import combinations
from functools import reduce

def get_devisors(n):
    f = [f for f,e in list(factorGenerator(n)) for i in range(e)]
    fc = [x for l in range(len(f)+1) for x in combinations(f, l)]
    devisors = [1 if c==() else reduce((lambda x, y: x * y), c) for c in set(fc)]
    return sorted(devisors)
Sadiq
fonte
0

Se o seu PC tem muita memória, uma única linha bruta pode ser rápida o suficiente com numpy:

N = 10000000; tst = np.arange(1, N); tst[np.mod(N, tst) == 0]
Out: 
array([      1,       2,       4,       5,       8,      10,      16,
            20,      25,      32,      40,      50,      64,      80,
           100,     125,     128,     160,     200,     250,     320,
           400,     500,     625,     640,     800,    1000,    1250,
          1600,    2000,    2500,    3125,    3200,    4000,    5000,
          6250,    8000,   10000,   12500,   15625,   16000,   20000,
         25000,   31250,   40000,   50000,   62500,   78125,   80000,
        100000,  125000,  156250,  200000,  250000,  312500,  400000,
        500000,  625000, 1000000, 1250000, 2000000, 2500000, 5000000])

Demora menos de 1 segundo no meu PC lento.

Mathieu Villion
fonte
0

Minha solução via função de gerador é:

def divisor(num):
    for x in range(1, num + 1):
        if num % x == 0:
            yield x
    while True:
        yield None
Eugene
fonte
-1
return [x for x in range(n+1) if n/x==int(n/x)]
user3697453
fonte
3
O questionador pediu um algoritmo melhor, não apenas um formato mais bonito.
Veedrac 01 de
4
Você precisa usar intervalo (1, n + 1) para evitar a divisão por zero. Além disso, você precisa usar float (n) para a primeira divisão se estiver usando Python 2.7, aqui 1/2 = 0
Jens Munk
-1

Para mim, isso funciona bem e também é limpo (Python 3)

def divisors(number):
    n = 1
    while(n<number):
        if(number%n==0):
            print(n)
        else:
            pass
        n += 1
    print(number)

Não é muito rápido, mas retorna os divisores linha por linha conforme desejado. Você também pode fazer list.append (n) e list.append (número) se realmente quiser

tomekch6
fonte