Apa cara terbaik untuk mendapatkan semua pembagi suatu bilangan?

108

Inilah cara yang sangat bodoh:

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

Hasil yang ingin saya dapatkan mirip dengan yang ini, tetapi saya ingin algoritme yang lebih cerdas (yang ini terlalu lambat dan bodoh :-)

Saya dapat menemukan faktor prima dan multiplisitasnya dengan cukup cepat. Saya memiliki generator yang menghasilkan faktor dengan cara ini:

(faktor1, multiplisitas1)
(faktor2, multiplisitas2)
(faktor3, multiplisitas3)
dan seterusnya ...

yaitu keluaran dari

for i in factorGenerator(100):
    print i

adalah:

(2, 2)
(5, 2)

Saya tidak tahu seberapa berguna ini untuk apa yang ingin saya lakukan (saya mengkodekannya untuk masalah lain), bagaimanapun saya ingin cara yang lebih cerdas untuk membuatnya

for i in divisorGen(100):
    print i

keluaran ini:

1
2
4
5
10
20
25
50
100

PEMBARUAN: Terima kasih banyak kepada Greg Hewgill dan "cara cerdasnya" :) Menghitung semua pembagi dari 100000000 membutuhkan 0,01 detik dengan caranya melawan angka 39 yang dilakukan oleh cara bodoh pada mesin saya, sangat keren: D

UPDATE 2: Berhenti mengatakan ini adalah duplikat dari posting ini . Menghitung jumlah pembagi dari bilangan tertentu tidak perlu menghitung semua pembaginya. Ini masalah yang berbeda, jika menurut Anda tidak, cari "Fungsi pembagi" di wikipedia. Bacalah soal dan jawabannya sebelum posting, jika belum paham topiknya jangan tambah tidak berguna dan sudah diberi jawaban.

Andrea Ambu
sumber
Alasan disarankan bahwa pertanyaan ini hampir merupakan duplikat dari "Algoritme untuk menghitung jumlah pembagi dari bilangan tertentu" adalah karena langkah pertama yang disarankan dalam pertanyaan itu adalah menemukan semua pembagi , yang saya yakini persis apa yang kamu coba lakukan?
Andrew Edgecombe
4
Andrew untuk mencari berapa banyak pembagi yang ada, Anda hanya perlu mencari faktor prima dan menggunakannya untuk menghitung banyaknya pembagi yang mungkin ada. Menemukan pembagi tidak diperlukan dalam kasus itu.
Loïc Faure-Lacroix
1
@Andrea Ambu, harap perbaiki nama fungsi Anda
mineral

Jawaban:

77

Mengingat factorGeneratorfungsi Anda , berikut ini divisorGenyang harus berfungsi:

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

Efisiensi keseluruhan dari algoritma ini akan bergantung sepenuhnya pada efisiensi dari factorGenerator.

Greg Hewgill
sumber
2
wow butuh 0,01 untuk menghitung semua pembagi 100000000 melawan 39 yang mengambil cara bodoh (berhenti di n / 2) sangat keren, terima kasih!
Andrea Ambu
47
Bagi kita yang tidak mengerti Python, apa sebenarnya yang dilakukan ini?
Matthew Scharley
1
monoksida: ini menghitung semua kombinasi perkalian dari faktor-faktor yang diberikan. Sebagian besar harus cukup jelas; garis "hasil" seperti pengembalian tetapi terus berjalan setelah mengembalikan nilai. [0] * nfactors membuat daftar nol dengan panjang nfactors. mengurangi (...) menghitung produk dari faktor.
Greg Hewgill
Notasi reduce dan lambda adalah bagian yang sebenarnya membuatku bingung. Saya mencoba menerapkan algoritme untuk melakukan ini di C # menggunakan fungsi rekursif untuk menjalankan berbagai faktor dan mengalikan semuanya, tetapi tampaknya memiliki kinerja yang buruk pada angka seperti 1024 yang memiliki banyak faktor
Matthew Scharley
3
Ini tentu saja secara dramatis lebih baik daripada membaginya dengan setiap angka hingga n / 2 atau bahkan sqrt (n), tetapi implementasi khusus ini memiliki dua kelemahan: cukup tidak efektif: banyak perkalian dan eksponensial, berulang kali mengalikan pangkat yang sama dll. Terlihat Pythonic, tapi menurut saya Python bukan tentang mematikan kinerja. Masalah dua: pembagi tidak dikembalikan secara berurutan.
Tomasz Gandor
34

Untuk memperluas apa yang dikatakan Shimi, Anda seharusnya hanya menjalankan perulangan dari 1 ke akar kuadrat dari n. Kemudian untuk menemukan pasangannya, lakukan n / i, dan ini akan mencakup seluruh ruang masalah.

Seperti yang juga dicatat, ini adalah NP, atau masalah 'sulit'. Pencarian menyeluruh, cara Anda melakukannya, sama bagusnya dengan mendapatkan jawaban yang terjamin. Fakta ini digunakan oleh algoritma enkripsi dan sejenisnya untuk membantu mengamankannya. Jika seseorang memecahkan masalah ini, sebagian besar, jika tidak semua, komunikasi 'aman' kita saat ini akan menjadi tidak aman.

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

Yang seharusnya menampilkan daftar seperti:

[1, 2, 4, 5, 10, 20, 25, 50, 100]
Matthew Scharley
sumber
2
Karena, setelah Anda memiliki daftar elemen antara 1..10, Anda dapat menghasilkan elemen antara 11..100 sepele. Anda mendapatkan {1, 2, 4, 5, 10}. Bagilah 100 dengan masing-masing elemen ini dan Anda {100, 50, 20, 25, 10}.
Matthew Scharley
2
Faktor SELALU dihasilkan berpasangan, berdasarkan definisi. Dengan hanya menelusuri ke sqrt (n), Anda memotong pekerjaan Anda dengan pangkat 2.
Matthew Scharley
Ini sangat lebih cepat daripada versi di posting saya, tetapi masih terlalu lambat daripada versi yang menggunakan faktor utama
Andrea Ambu
Saya setuju ini bukan solusi terbaik. Saya hanya menunjukkan cara 'lebih baik' untuk melakukan pencarian 'bodoh' yang akan menghemat banyak waktu.
Matthew Scharley
Faktorisasi belum terbukti NP-hard. en.wikipedia.org/wiki/Integer_factorization Dan masalahnya adalah menemukan semua pembagi yang diberikan bahwa faktor prima (bagian yang sulit) telah ditemukan.
Jamie
19

Meski sudah ada banyak solusi untuk ini, saya benar-benar harus memposting ini :)

Yang ini adalah:

  • mudah dibaca
  • pendek
  • mandiri, siap salin & tempel
  • cepat (dalam kasus dengan banyak faktor prima dan pembagi,> 10 kali lebih cepat dari solusi yang diterima)
  • sesuai dengan python3, python2 dan pypy

Kode:

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
sumber
Saya akan mengganti while i*i <= nndengan while i <= limit, di manalimit = math.sqrt(n)
Rafa0809
17

Saya pikir Anda bisa berhenti di math.sqrt(n)bukannya n / 2.

Saya akan memberikan contoh agar Anda dapat memahaminya dengan mudah. Sekarang sqrt(28)sudah 5.29jadi ceil(5.29)6. Jadi saya jika saya berhenti di 6 maka saya akan bisa mendapatkan semua pembagi. Bagaimana?

Pertama lihat kodenya lalu lihat gambar:

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

Sekarang, lihat gambar di bawah ini:

Katakanlah saya telah menambahkan 1ke daftar pembagi saya dan saya mulai dengan i=2itu

Pembagi dari 28

Jadi pada akhir semua iterasi karena saya telah menambahkan hasil bagi dan pembagi ke daftar saya, semua pembagi dari 28 diisi.

Sumber: Cara menentukan pembagi suatu bilangan

Anivarth
sumber
2
Bagus bagus!! math.sqrt(n) instead of n/2wajib untuk keanggunan
Rafa0809
Ini salah Anda lupa n habis dibagi dengan sendirinya.
jasonleonhard
1
Jawaban bagus. Sederhana dan jelas. Tetapi untuk python 3 ada 2 perubahan yang diperlukan: n / i harus diketik menggunakan int (n / i) karena n / i menghasilkan angka float. Rangex juga tidak digunakan lagi di python 3 dan telah digantikan oleh range.
Geoffroy CALA
7

Saya suka solusi Greg, tapi saya berharap itu lebih seperti python. Saya merasa ini akan lebih cepat dan lebih mudah dibaca; jadi setelah beberapa waktu pengkodean saya keluar dengan ini.

Dua fungsi pertama diperlukan untuk membuat produk kartesian dari daftar. Dan dapat digunakan kembali setiap kali masalah ini muncul. Ngomong-ngomong, saya harus memprogramnya sendiri, jika ada yang tahu solusi standar untuk masalah ini, jangan ragu untuk menghubungi saya.

"Factorgenerator" sekarang menampilkan kamus. Dan kemudian kamus dimasukkan ke dalam "pembagi", yang menggunakannya untuk menghasilkan daftar pertama, di mana setiap daftar adalah daftar faktor dalam bentuk p ^ n dengan p prime. Kemudian kami membuat perkalian kartesian dari daftar tersebut, dan akhirnya kami menggunakan solusi Greg untuk menghasilkan pembagi. Kami menyortirnya, dan mengembalikannya.

Saya mengujinya dan tampaknya sedikit lebih cepat dari versi sebelumnya. Saya mengujinya sebagai bagian dari program yang lebih besar, jadi saya tidak bisa benar-benar mengatakan berapa cepatnya.

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 ini adalah pertama kalinya saya memposting ke stackoverflow. Saya menantikan tanggapan apa pun.

Pietro Speroni
sumber
Di Python 2.6 ada itertools.product ().
jfs
Versi yang menggunakan generator dan bukan list.append di mana-mana bisa lebih bersih.
jfs
Saringan Eratosthenes dapat digunakan untuk menghasilkan bilangan prima kurang dari atau sama dengan akar persegi (n) stackoverflow.com/questions/188425/project-euler-problem#193605
jfs
1
Gaya pengkodean: eksponen = [k ** x untuk k, v dalam faktor.items () untuk x dalam rentang (v + 1)]
jfs
Untuk listexponents: [[k ** x untuk x dalam range (v + 1)] untuk k, v dalam factor.items ()]
klenwell
3

Berikut adalah cara cerdas dan cepat untuk melakukannya untuk angka hingga dan sekitar 10 ** 16 dengan Python 3.6 murni,

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
sumber
Apa nama algoritma yang digunakan untuk mencari bilangan prima dan memfaktorkannya? Karena saya ingin menerapkan ini di C # ..
Kyu96
2

Diadaptasi dari CodeReview , berikut adalah varian yang bisa digunakan 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
sumber
1
Saya tampaknya akan mendapatkan kesalahan: NameError: global name 'prime_factors' is not defined. Tak satu pun dari jawaban lain, atau pertanyaan asli, yang menjelaskan apa yang dilakukannya.
AnnanFay
2

Saya hanya akan menambahkan versi Anivarth yang sedikit direvisi (karena saya yakin ini yang paling pythonic) untuk referensi di masa mendatang.

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
sumber
1

Pertanyaan lama, tapi inilah pendapat saya:

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

Anda dapat melakukan proxy dengan:

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

CATATAN: Untuk bahasa yang mendukung, ini dapat berupa rekursif ekor.

joksnet.dll
sumber
0

Dengan asumsi bahwa factorsfungsi tersebut mengembalikan faktor dari n (misalnya, factors(60)mengembalikan daftar [2, 2, 3, 5]), berikut adalah fungsi untuk menghitung pembagi dari 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
pengguna448810
sumber
Apa itu python? Bagaimanapun, ini bukan python 3.x pasti.
GinKin
Ini pseudocode, yang seharusnya mudah diterjemahkan ke python.
user448810
Terlambat 3 tahun, lebih baik terlambat daripada tidak sama sekali :) IMO, ini adalah kode paling sederhana dan terpendek untuk melakukan ini. Saya tidak memiliki tabel perbandingan, tetapi saya dapat memfaktorkan dan menghitung pembagi hingga satu juta dalam 1 di laptop portabel i5 saya.
Riyaz Mansoor
0

Inilah solusi saya. Tampaknya bodoh tetapi berfungsi dengan baik ... dan saya mencoba menemukan semua pembagi yang tepat sehingga loop dimulai dari 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
sumber
salah ketik: fakta kembali => daftar wajah kembali
Jonath P
0

Jika Anda hanya peduli tentang menggunakan pemahaman daftar dan tidak ada hal lain yang penting bagi Anda!

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)
Shadiq
sumber
0

Jika PC Anda memiliki banyak memori, satu baris yang kasar bisa cukup cepat dengan 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])

Membutuhkan waktu kurang dari 1 detik di PC saya yang lambat.

Mathieu Villion
sumber
0

Solusi saya melalui fungsi generator adalah:

def divisor(num):
    for x in range(1, num + 1):
        if num % x == 0:
            yield x
    while True:
        yield None
Eugene
sumber
-1
return [x for x in range(n+1) if n/x==int(n/x)]
pengguna3697453
sumber
3
Penanya meminta algoritma yang lebih baik, bukan hanya format yang lebih bagus.
Veedrac
4
Anda perlu menggunakan rentang (1, n + 1) untuk menghindari pembagian dengan nol. Juga, Anda perlu menggunakan float (n) untuk divisi pertama jika menggunakan Python 2.7, di sini 1/2 = 0
Jens Munk
-1

Bagi saya ini berfungsi dengan baik dan juga bersih (Python 3)

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

Tidak terlalu cepat tetapi mengembalikan pembagi baris demi baris seperti yang Anda inginkan, Anda juga dapat melakukan list.append (n) dan list.append (nomor) jika Anda benar-benar ingin

tomekch6
sumber