Bir sayının tüm bölenlerini almanın en iyi yolu nedir?


108

İşte en aptalca yol:

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

Elde etmek istediğim sonuç buna benziyor, ancak daha akıllı bir algoritma istiyorum (bu çok yavaş ve aptal :-)

Asal çarpanları ve çokluklarını yeterince hızlı bulabilirim. Bu şekilde faktör üreten bir jeneratörüm var:

(faktör1, çokluk1)
(faktör2, çokluk2)
(faktör3, çokluk3)
ve benzeri ...

yani çıktısı

for i in factorGenerator(100):
    print i

dır-dir:

(2, 2)
(5, 2)

Bunun yapmak istediğim şey için ne kadar yararlı olduğunu bilmiyorum (başka problemler için kodladım), yine de yapmak için daha akıllı bir yol istiyorum

for i in divisorGen(100):
    print i

bu çıktı:

1
2
4
5
10
20
25
50
100

GÜNCELLEME: Greg Hewgill'e ve onun "akıllı yolu" na çok teşekkürler :) 100000000'ün tüm bölenlerini hesaplamak, makinemde aptalca yolun aldığı 39'lara karşı 0,01 saniye sürdü, çok havalı: D

GÜNCELLEME 2: Bunun bu gönderinin kopyası olduğunu söylemeyi bırakın . Belirli bir sayının bölen sayısını hesaplamak, tüm bölenleri hesaplamak zorunda değildir. Bu farklı bir problem, eğer olmadığını düşünüyorsanız, wikipedia'da "Divisor işlevi" ni arayın. Yayınlamadan önce soruları ve cevabı okuyun, konunun ne olduğunu anlamadıysanız, sadece yararlı olmayan ve zaten verilen cevapları eklemeyin.


Bu sorunun "belirli bir sayının bölenlerinin sayısını hesaplayan algoritmanın" neredeyse bir kopyası olmasının nedeni, bu sorudaki önerilen ilk adımın , tam olarak olduğuna inandığım tüm bölenleri bulmaktı. ne yapmaya çalışıyordun
Andrew Edgecombe

4
Andrew, kaç bölen olduğunu bulmak için asal çarpanları bulmalı ve sonra onları ne kadar bölen olabileceğini saymak için kullanmalısın. Bölenleri bulmak bu durumda gerekli değildir.
Loïc Faure-Lacroix

1
@Andrea Ambu, lütfen işlev adlarınızı düzeltin
minerals

Yanıtlar:


77

Senin Verilen factorGeneratorfonksiyon, burada olduğunu divisorGençalışması gerektiğini:

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

Bu algoritmanın genel verimliliği tamamen factorGenerator.


2
vay canına, 100000000'ün tüm bölenlerini hesaplamak için, aptalca davranan 39'lara karşı (n / 2'de durarak) çok havalı, teşekkürler!
Andrea Ambu

47
Pythonese'yi anlamayan bizler için bu aslında ne yapıyor?
Matthew Scharley

1
monoksit: bu, verilen faktörlerin tüm çarpımsal kombinasyonlarını hesaplar. Çoğu kendi kendini açıklayıcı nitelikte olmalıdır; "getiri" satırı bir getiri gibidir, ancak bir değer döndürdükten sonra devam eder. [0] * nfactors, uzunluk n faktörlerinin sıfırlarının bir listesini oluşturur. azaltma (...) faktörlerin çarpımını hesaplar.
Greg Hewgill

Azaltma ve lambda notasyonu kafamı karıştıran kısımlardı. Bunu yapmak için C # 'da bir algoritma uygulamayı denedim, bir dizi faktörde yürümek ve hepsini bir arada çarpmak için özyinelemeli bir fonksiyon kullanarak, ancak 1024 gibi birçok faktörü olan sayılarda korkunç bir performansa sahip gibi görünüyor
Matthew Scharley

3
Bu, elbette, n / 2'ye veya hatta sqrt (n) 'ye kadar olan her sayıya bölmekten çarpıcı biçimde daha iyidir, ancak bu özel uygulamanın iki dezavantajı vardır: oldukça etkisiz: tonlarca çarpma ve üs alma, aynı güçleri tekrar tekrar çarpma vb. Pythonic, ancak Python'un performansı düşürmekle ilgili olduğunu düşünmüyorum. İkinci sorun: Bölenler sırayla iade edilmiyor.
Tomasz Gandor

34

Shimi'nin söylediklerini genişletmek için, döngünüzü yalnızca 1'den n'nin kareköküne kadar çalıştırmalısınız. Sonra çifti bulmak için yapın n / ive bu tüm sorun alanını kapsayacaktır.

Yine belirtildiği gibi, bu bir NP veya 'zor' bir sorundur. Kapsamlı arama, bunu yapma şekliniz, garantili cevaplar için olduğu kadar iyidir. Bu gerçek, şifreleme algoritmaları ve benzerleri tarafından bunların güvenliğini sağlamak için kullanılır. Birisi bu sorunu çözerse, mevcut 'güvenli' iletişimimizin tamamı olmasa da çoğu güvensiz hale gelir.

Python kodu:

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

Aşağıdakiler gibi bir liste çıkarması gerekir:

[1, 2, 4, 5, 10, 20, 25, 50, 100]

2
Çünkü, 1..10 arasında bir eleman listesine sahip olduğunuzda, 11..100 arasındaki herhangi bir elemanı önemsiz bir şekilde üretebilirsiniz. {1, 2, 4, 5, 10} elde edersiniz. 100'ü bu öğelerin her birine ve siz {100, 50, 20, 25, 10} 'a bölün.
Matthew Scharley

2
Faktörler HER ZAMAN tanım sayesinde çiftler halinde üretilir. Sadece sqrt (n) 'yi arayarak, çalışmanızı 2 kuvvetle
kesiyorsunuz

Gönderimdeki versiyondan çok daha hızlı, ancak yine de asal faktörleri kullanan versiyondan çok yavaş
Andrea Ambu

Bunun en iyi çözüm olmadığına katılıyorum. Basitçe, zaten çok zaman kazandıracak 'aptalca' aramayı yapmanın 'daha iyi' bir yolunu işaret ediyordum.
Matthew Scharley

Çarpanlara ayırmanın NP-zor olduğu gösterilmemiştir. en.wikipedia.org/wiki/Integer_factorization Ve sorun, asal çarpanlar (zor kısım) zaten bulunmuş olduğu için tüm bölenleri bulmaktı.
Jamie

19

Buna zaten birçok çözüm olmasına rağmen, bunu gerçekten göndermem gerekiyor :)

Bu:

  • okunabilir
  • kısa
  • kendi kendine yeten, kopyala ve yapıştır hazır
  • hızlı (çok sayıda asal çarpanın ve bölenin olduğu durumlarda, kabul edilen çözümden> 10 kat daha hızlı)
  • python3, python2 ve pypy uyumlu

Kod:

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

Ben yerini alacak while i*i <= nntarafından while i <= limitnerede,limit = math.sqrt(n)
Rafa0809

17

Bence math.sqrt(n)n / 2 yerine durabilirsin .

Kolayca anlayabilmeniz için size örnek vereceğim. Şimdi sqrt(28)ise 5.29bu yüzden ceil(5.29)ben o zaman ben tüm bölenler alabilirsiniz olacak 6 duracaktır eğer 6. Yani olacaktır. Nasıl?

Önce kodu görün ve ardından resme bakın:

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

Şimdi aşağıdaki resme bakın:

Lets Zaten eklemiş söylemek 1benim bölenler listesine ve ben ile başlayan i=2bu yüzden

28'in çarpanları

Dolayısıyla, tüm yinelemelerin sonunda, bölüm ve bölenleri listeme ekledikçe, 28'in tüm bölenleri doldurulur.

Kaynak: Bir sayının bölenleri nasıl belirlenir


2
İyi iyi!! math.sqrt(n) instead of n/2zarafet için zorunludur
Rafa0809

Bu yanlış. N'nin kendi kendine bölünebileceğini unuttunuz.
jasonleonhard

1
Güzel cevap. Basit ve anlaşılır. Ancak python 3 için 2 gerekli değişiklik vardır: n / i int (n / i) kullanılarak yazılmalıdır, çünkü n / i float sayı üretir. Ayrıca rangex, python 3'te kullanımdan kaldırılmıştır ve aralık ile değiştirilmiştir.
Geoffroy CALA

7

Greg çözümünü beğendim, ama keşke daha fazla python gibi olsaydı. Daha hızlı ve daha okunaklı olacağını hissediyorum; bu yüzden bir süre kodlamadan sonra bununla çıktım.

Listelerin kartezyen çarpımını yapmak için ilk iki işleve ihtiyaç vardır. Ve bu problemin ortaya çıktığı her yerde tekrar kullanılabilir. Bu arada, bunu kendim programlamam gerekiyordu, eğer bu sorun için standart bir çözüm bilen varsa, lütfen benimle iletişime geçmekten çekinmeyin.

"Factorgenerator" artık bir sözlük döndürüyor. Ve sonra sözlük "bölenler" e beslenir, bu onu ilk olarak bir liste listesi oluşturmak için kullanır, burada her liste p üssü ile p ^ n formunun faktörlerinin listesidir. Sonra bu listelerin kartezyen çarpımını yaparız ve sonunda bölen oluşturmak için Greg'in çözümünü kullanırız. Onları ayırıyoruz ve iade ediyoruz.

Test ettim ve önceki sürümden biraz daha hızlı görünüyor. Daha büyük bir programın parçası olarak test ettim, bu yüzden ne kadar hızlı olduğunu gerçekten söyleyemem.

Pietro Speroni (pietrosperoni nokta)

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, stackoverflow'a ilk kez gönderiyorum. Herhangi bir geri bildirim için sabırsızlanıyorum.


Python 2.6'da bir itertools.product () vardır.
jfs

Her yerde list.append yerine üreteçleri kullanan bir sürüm daha temiz olabilir.
jfs

Eratosthenes Sieve, sqrt (n) 'den küçük veya ona eşit asal sayılar üretmek için kullanılabilir stackoverflow.com/questions/188425/project-euler-problem#193605
jfs

1
Kodlama stili: üsler = [k için k ** x, faktörlerde. Öğeler () aralıktaki x için (v + 1)]
jfs

Liste katılımcıları için: [[k ** x aralıktaki x için (v + 1)] k için, v in
Factor.items

3

İşte saf Python 3.6'da 10 ** 16'ya kadar olan sayılar için bunu yapmanın akıllı ve hızlı bir yolu,

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

Asal sayıları bulmak ve çarpanlara ayırmak için kullanılan algoritmanın adı nedir? Çünkü bunu C # içinde uygulamak istiyorum.
Kyu96

2

CodeReview'den uyarlanan , işte birlikte çalışan bir varyant 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
Hatayla gibi görünüyor: NameError: global name 'prime_factors' is not defined. Diğer yanıtların hiçbiri veya orijinal soru bunun ne yaptığını tanımlamıyor.
AnnanFay

2

Gelecekte referans olması için Anivarth'ın biraz revize edilmiş bir versiyonunu (en pitonik olduğuna inandığım gibi) ekleyeceğim.

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

Eski soru, ama işte benim fikrim:

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

Şunlarla proxy yapabilirsiniz:

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

NOT: Destekleyen diller için bu, kuyruk özyinelemeli olabilir.


0

factorsİşlevin n'nin faktörlerini döndürdüğünü varsayarsak (örneğin, factors(60)[2, 2, 3, 5] listesini döndürür), burada n'nin bölenlerini hesaplamak için bir işlev vardır :

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

Bu piton mu? Her neyse, kesinlikle python 3.x değil.
GinKin

Python'a çevrilmesi basit olması gereken sözde koddur.
user448810

3 yıl geç, hiç olmamasından daha iyi geç :) IMO, bunu yapmak için en basit, en kısa kod budur. Bir karşılaştırma tablom yok, ancak i5 taşınabilir dizüstü bilgisayarımda 1 saniyede bir milyona kadar bölenleri çarpanlara ayırabilir ve hesaplayabilirim.
Riyaz Mansoor

0

İşte benim çözümüm. Aptalca görünüyor ama iyi çalışıyor ... ve tüm uygun bölenleri bulmaya çalışıyordum, böylece döngü i = 2'den başladı.

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

yazım hatası: gerçekleri döndür => faclist'i döndür
Jonath P

0

Yalnızca liste anlamalarını kullanmayı önemsiyorsanız ve sizin için başka hiçbir şey önemli değilse!

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

Bilgisayarınızda tonlarca bellek varsa, kaba bir tek satır numpy ile yeterince hızlı olabilir:

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

Yavaş bilgisayarımda 1 saniyeden az sürer.


0

Jeneratör işlevi aracılığıyla çözümüm:

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
Soru soran kişi, sadece daha güzel bir format değil, daha iyi bir algoritma istedi.
Veedrac 01

4
Sıfıra bölmekten kaçınmak için aralık (1, n + 1) kullanmanız gerekir. Ayrıca, Python 2.7 kullanıyorsanız ilk bölüm için float (n) kullanmanız gerekir, burada 1/2 = 0
Jens Munk

-1

Benim için bu iyi çalışıyor ve aynı zamanda temiz (Python 3)

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

Çok hızlı değil ancak bölenleri istediğiniz gibi satır satır döndürür, ayrıca gerçekten istiyorsanız list.append (n) ve list.append (number) yapabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.