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.


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.


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]

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

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


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.


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))

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))

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

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.


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

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

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)

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.


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

-1
return [x for x in range(n+1) if n/x==int(n/x)]

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.