วิธีที่ดีที่สุดในการหาตัวหารของจำนวนทั้งหมดคืออะไร?


109

นี่เป็นวิธีที่โง่มาก:

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

ผลลัพธ์ที่ฉันต้องการจะคล้ายกับผลลัพธ์นี้ แต่ฉันต้องการอัลกอริทึมที่ชาญฉลาดกว่านี้ (อันนี้ช้าและโง่เกินไป :-)

ฉันสามารถหาปัจจัยเฉพาะและความทวีคูณได้เร็วพอ ฉันมีเครื่องกำเนิดไฟฟ้าที่สร้างปัจจัยด้วยวิธีนี้:

(factor1, multiplicity1)
(factor2, multiplicity2)
(factor3, multiplicity3)
และอื่น ๆ ...

คือผลลัพธ์ของ

for i in factorGenerator(100):
    print i

คือ:

(2, 2)
(5, 2)

ฉันไม่รู้ว่าสิ่งนี้มีประโยชน์แค่ไหนสำหรับสิ่งที่ฉันต้องการทำ (ฉันเขียนโค้ดสำหรับปัญหาอื่น ๆ ) อย่างไรก็ตามฉันต้องการวิธีที่ชาญฉลาดกว่านี้ในการทำ

for i in divisorGen(100):
    print i

ผลลัพธ์นี้:

1
2
4
5
10
20
25
50
100

UPDATE:ขอบคุณมากสำหรับ Greg Hewgill และ "วิธีที่ชาญฉลาด" ของเขา :) การคำนวณตัวหารทั้งหมดของ 100000000 ใช้เวลา 0.01 วินาทีเมื่อเทียบกับ 39s ที่วิธีโง่ ๆ ใช้กับเครื่องของฉันเจ๋งมาก: D

อัปเดต 2:หยุดพูดว่านี่ซ้ำกับโพสต์นี้ การคำนวณจำนวนตัวหารของจำนวนที่กำหนดไม่จำเป็นต้องคำนวณตัวหารทั้งหมด เป็นปัญหาที่แตกต่างหากคุณคิดว่าไม่ใช่ให้มองหา "ฟังก์ชันตัวหาร" ในวิกิพีเดีย อ่านคำถามและคำตอบก่อนโพสต์หากคุณไม่เข้าใจว่าหัวข้อคืออะไรอย่าเพิ่มคำตอบที่ไม่มีประโยชน์และได้รับคำตอบแล้ว


เหตุผลที่แนะนำว่าคำถามนี้เกือบจะซ้ำกันของ "อัลกอริทึมในการคำนวณจำนวนตัวหารของจำนวนที่กำหนด" คือขั้นตอนแรกที่แนะนำในคำถามนั้นคือการหาตัวหารทั้งหมดซึ่งฉันเชื่อว่าตรง คุณพยายามทำอะไร?
Andrew Edgecombe

4
แอนดรูเพื่อหาจำนวนตัวหารที่มีคุณต้องหาตัวประกอบเฉพาะจากนั้นใช้มันเพื่อนับจำนวนตัวหารที่อาจมี ในกรณีนั้นไม่จำเป็นต้องหาตัวหาร
Loïc Faure-Lacroix

1
@Andrea Ambu โปรดแก้ไขชื่อฟังก์ชัน
แร่ธาตุ

คำตอบ:


77

จากfactorGeneratorฟังก์ชั่นของคุณนี่คือสิ่งdivisorGenที่ควรใช้:

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

ประสิทธิภาพโดยรวมของอัลกอริทึมนี้จะขึ้นอยู่กับประสิทธิภาพของไฟล์factorGenerator.


2
ว้าวมันใช้เวลา 0.01 ในการคำนวณตัวหารทั้งหมด 100000000 เทียบกับ 39 ที่ใช้วิธีโง่ ๆ (หยุดที่ n / 2) เจ๋งมากขอบคุณ!
Andrea Ambu

47
สำหรับพวกเราที่ไม่เข้าใจ Pythonese สิ่งนี้กำลังทำอะไรอยู่?
Matthew Scharley

1
โมโนออกไซด์: สิ่งนี้จะคำนวณการรวมกันของปัจจัยที่กำหนด ส่วนใหญ่ควรอธิบายด้วยตนเอง เส้น "ผลตอบแทน" เป็นเหมือนผลตอบแทน แต่จะดำเนินต่อไปหลังจากส่งคืนค่า [0] * nfactors สร้างรายการค่าศูนย์ของ nfactors ความยาว ลด (... ) คำนวณผลคูณของปัจจัย
Greg Hewgill

สัญกรณ์ลดและแลมบ์ดาเป็นส่วนที่ทำให้ฉันสับสน ฉันลองใช้อัลกอริทึมเพื่อทำสิ่งนี้ใน C # โดยใช้ฟังก์ชันเรียกซ้ำเพื่อเดินอาร์เรย์ของปัจจัยและคูณทั้งหมดเข้าด้วยกัน แต่ดูเหมือนว่าจะมีประสิทธิภาพที่น่ากลัวสำหรับตัวเลขเช่น 1024 ที่มีหลายปัจจัย
Matthew Scharley

3
แน่นอนว่านี่ดีกว่าการหารด้วยทุกจำนวนมากถึง n / 2 หรือแม้แต่ sqrt (n) แต่การนำไปใช้งานนี้มีข้อเสียอยู่สองประการคือค่อนข้างไม่สมบูรณ์: การคูณและการยกกำลังจำนวนมากการคูณพลังเดียวกันซ้ำ ๆ เป็นต้นดู Pythonic แต่ฉันไม่คิดว่า Python เกี่ยวกับประสิทธิภาพการฆ่า ปัญหาที่สอง: ตัวหารจะไม่ถูกส่งกลับตามลำดับ
Tomasz Gandor

34

หากต้องการขยายความในสิ่งที่ Shimi พูดคุณควรจะวิ่งวนจาก 1 ถึงรากที่สองของ n เท่านั้น จากนั้นหาคู่ให้ทำn / iและสิ่งนี้จะครอบคลุมพื้นที่ปัญหาทั้งหมด

ตามที่ระบุไว้นี้เป็นปัญหา NP หรือ 'ยาก' การค้นหาที่ละเอียดถี่ถ้วนในแบบที่คุณทำนั้นดีพอ ๆ กับที่จะได้รับคำตอบที่รับประกัน ข้อเท็จจริงนี้ใช้โดยอัลกอริทึมการเข้ารหัสและอื่น ๆ ที่คล้ายกันเพื่อช่วยรักษาความปลอดภัย หากมีคนช่วยแก้ปัญหานี้ส่วนใหญ่ถ้าไม่ใช่ทั้งหมดของการสื่อสารที่ 'ปลอดภัย' ในปัจจุบันของเราจะแสดงผลไม่ปลอดภัย

รหัส 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))

ซึ่งควรแสดงรายการเช่น:

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

2
เนื่องจากเมื่อคุณมีรายการองค์ประกอบระหว่าง 1..10 คุณสามารถสร้างองค์ประกอบใดก็ได้ระหว่าง 11..100 เล็กน้อย คุณจะได้รับ {1, 2, 4, 5, 10} หาร 100 ด้วยแต่ละองค์ประกอบและคุณ {100, 50, 20, 25, 10}
Matthew Scharley

2
ปัจจัยมักเกิดขึ้นเป็นคู่ ๆ โดยอาศัยคำจำกัดความ ด้วยการค้นหาเพียง sqrt (n) คุณกำลังตัดงานของคุณด้วยกำลัง 2
Matthew Scharley

เร็วกว่าเวอร์ชันในโพสต์ของฉันมาก แต่ก็ยังช้าเกินไปกว่าเวอร์ชันที่ใช้ปัจจัยสำคัญ
Andrea Ambu

ฉันยอมรับว่านี่ไม่ใช่ทางออกที่ดีที่สุด ฉันเพียงแค่ชี้ให้เห็นวิธีที่ 'ดีกว่า' ในการค้นหาแบบ 'โง่' ซึ่งจะช่วยประหยัดเวลาได้มากแล้ว
Matthew Scharley

การแยกตัวประกอบไม่ได้แสดงให้เห็นว่าเป็น NP-hard en.wikipedia.org/wiki/Integer_factorization และปัญหาคือการหาตัวหารทั้งหมดเนื่องจากพบว่าปัจจัยเฉพาะ (ส่วนที่ยาก) แล้ว
Jamie

19

แม้ว่าจะมีวิธีแก้ไขมากมายอยู่แล้ว แต่ฉันก็ต้องโพสต์สิ่งนี้ :)

อันนี้คือ:

  • อ่านได้
  • สั้น
  • มีอยู่ในตัวคัดลอกและวางพร้อม
  • รวดเร็ว (ในกรณีที่มีปัจจัยเฉพาะและตัวหารจำนวนมากเร็วกว่าโซลูชันที่ยอมรับถึง 10 เท่า)
  • ตามมาตรฐาน python3, python2 และ pypy

รหัส:

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

ฉันจะแทนที่while i*i <= nnโดยwhile i <= limitที่limit = math.sqrt(n)
Rafa0809

17

ฉันคิดว่าคุณสามารถหยุดที่ math.sqrt(n)แทนที่จะเป็น n / 2

ฉันจะยกตัวอย่างให้คุณเข้าใจได้ง่าย ตอนนี้sqrt(28)เป็น5.29เช่นนั้นceil(5.29)จะ 6. ดังนั้นผมถ้าผมจะหยุดที่ 6 แล้วฉันจะได้รับหารทั้งหมด อย่างไร?

ก่อนอื่นให้ดูรหัสจากนั้นดูภาพ:

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

ตอนนี้ดูภาพด้านล่าง:

ช่วยบอกฉันได้เพิ่ม1ไปยังรายการตัวหารของฉันและฉันเริ่มต้นด้วยi=2ดังนั้น

ตัวหาร 28

ในตอนท้ายของการวนซ้ำทั้งหมดขณะที่ฉันเพิ่มผลหารและตัวหารในรายการของฉันตัวหารทั้งหมดของ 28 จะถูกเติม

ที่มา: วิธีกำหนดตัวหารของจำนวน


2
ดีดี!! math.sqrt(n) instead of n/2เป็นข้อบังคับเพื่อความสง่างาม
Rafa0809

สิ่งนี้ไม่ถูกต้อง คุณลืม n หารด้วยตัวมันเอง
jasonleonhard

1
คำตอบที่ดี เรียบง่ายและชัดเจน แต่สำหรับ python 3 มีการเปลี่ยนแปลงที่จำเป็น 2 ประการ: n / i ควรพิมพ์โดยใช้ int (n / i) ทำให้ n / i สร้างหมายเลขลอย rangex ยังเลิกใช้ใน python 3 และถูกแทนที่ด้วย range
Geoffroy CALA

7

ฉันชอบวิธีแก้ปัญหาของ Greg แต่ฉันหวังว่ามันจะเหมือน python มากกว่านี้ ฉันรู้สึกว่ามันจะเร็วและอ่านได้มากขึ้น หลังจากเขียนโค้ดได้ระยะหนึ่งฉันก็ออกมาพร้อมกับสิ่งนี้

จำเป็นต้องมีสองฟังก์ชันแรกเพื่อสร้างผลิตภัณฑ์คาร์ทีเซียนของรายการ และสามารถนำกลับมาใช้ใหม่ได้เมื่อเกิดปัญหานี้ขึ้น อย่างไรก็ตามฉันต้องตั้งโปรแกรมนี้ด้วยตัวเองหากใครรู้วิธีแก้ปัญหามาตรฐานสำหรับปัญหานี้โปรดอย่าลังเลที่จะติดต่อฉัน

ตอนนี้ "Factorgenerator" ส่งคืนพจนานุกรม จากนั้นพจนานุกรมจะถูกป้อนเป็น "ตัวหาร" ซึ่งเป็นผู้ที่ใช้ในการสร้างรายการรายการแรกโดยแต่ละรายการคือรายการของปัจจัยของรูปแบบ p ^ n ด้วย p ไพรม์ จากนั้นเราสร้างผลคูณคาร์ทีเซียนของรายการเหล่านั้นและในที่สุดเราก็ใช้วิธีแก้ปัญหาของเกร็กเพื่อสร้างตัวหาร เราจัดเรียงและส่งคืน

ฉันทดสอบแล้วและดูเหมือนว่าจะเร็วกว่าเวอร์ชันก่อนหน้าเล็กน้อย ฉันทดสอบเป็นส่วนหนึ่งของโปรแกรมที่ใหญ่กว่าดังนั้นฉันไม่สามารถบอกได้ว่ามันเร็วแค่ไหน

Pietro Speroni (pietrosperoni จุด)

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)

ป.ล. นี่เป็นครั้งแรกที่ฉันโพสต์ลงใน stackoverflow ฉันรอคอยสำหรับข้อเสนอแนะใด ๆ


ใน Python 2.6 มี itertools.product ()
jfs

เวอร์ชันที่ใช้เครื่องกำเนิดไฟฟ้าแทนรายการผนวกทุกที่อาจจะสะอาดกว่า
jfs

สามารถใช้ Sieve of Eratosthenes เพื่อสร้างจำนวนเฉพาะน้อยกว่าหรือเท่ากับ sqrt (n) stackoverflow.com/questions/188425/project-euler-problem#193605
jfs

1
รูปแบบการเข้ารหัส: เลขชี้กำลัง = [k ** x สำหรับ k, v ใน factor.items () สำหรับ x ในช่วง (v + 1)]
jfs

สำหรับ listexponents: [[k ** x สำหรับ x ในช่วง (v + 1)] สำหรับ k, v ในปัจจัยรายการ ()]
klenwell

3

นี่คือวิธีที่ชาญฉลาดและรวดเร็วในการทำเพื่อให้ได้ตัวเลขสูงถึง 10 ** 16 ใน Python 3.6 ล้วนๆ

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

ชื่อของอัลกอริทึมที่ใช้ในการค้นหาราคาและการแยกตัวประกอบคืออะไร? เพราะฉันต้องการใช้สิ่งนี้ใน C # ..
Kyu96

3

ฉันจะเพิ่ม Anivarth รุ่นปรับปรุงเล็กน้อย (เพราะฉันเชื่อว่ามันเป็น pythonic ที่สุด) สำหรับการอ้างอิงในอนาคต

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

2

ดัดแปลงมาจากCodeReviewนี่คือตัวแปรที่ใช้ได้กับ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
ดูเหมือนว่าฉันจะได้รับข้อผิดพลาด: NameError: global name 'prime_factors' is not defined. ไม่มีคำตอบอื่นใดหรือคำถามดั้งเดิมกำหนดว่าสิ่งนี้ทำอะไร
AnnanFay

1

คำถามเก่า แต่นี่คือสิ่งที่ฉันใช้:

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

คุณสามารถพร็อกซีด้วย:

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

หมายเหตุ: สำหรับภาษาที่รองรับอาจเป็นการเรียกซ้ำแบบหาง


0

สมมติว่าfactorsฟังก์ชันส่งคืนตัวประกอบของn (ตัวอย่างเช่นfactors(60)ส่งกลับรายการ [2, 2, 3, 5]) นี่คือฟังก์ชันในการคำนวณตัวหารของ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

งูเหลือมรึเปล่า ยังไงก็ไม่ใช่ python 3.x แน่นอน
GinKin

มันคือรหัสเทียมซึ่งควรแปลเป็น python ได้ง่าย
user448810

ช้าไป 3 ปีดีกว่าไม่มาเลย :) IMO นี่เป็นรหัสที่ง่ายและสั้นที่สุดในการทำเช่นนี้ ฉันไม่มีตารางเปรียบเทียบ แต่ฉันสามารถแยกตัวประกอบและคำนวณตัวหารได้มากถึงหนึ่งล้านใน 1 บนแล็ปท็อปพกพา i5 ของฉัน
Riyaz Mansoor

0

นี่คือทางออกของฉัน ดูเหมือนจะโง่ แต่ใช้ได้ดี ... และฉันพยายามหาตัวหารที่เหมาะสมทั้งหมดดังนั้นลูปจึงเริ่มจาก 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

typo: return fact => return faclist
Jonath P

0

หากคุณสนใจแค่การใช้ความเข้าใจในรายการและไม่มีอะไรสำคัญสำหรับคุณ!

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

หากพีซีของคุณมีหน่วยความจำมากมายเส้นเดี่ยวเดรัจฉานอาจเร็วพอด้วยตัวเลข:

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

ใช้เวลาน้อยกว่า 1 วินาทีบนพีซีที่ช้าของฉัน


0

วิธีแก้ปัญหาของฉันผ่านฟังก์ชั่นเครื่องกำเนิดไฟฟ้าคือ:

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
ผู้ถามถามหาอัลกอริทึมที่ดีกว่าไม่ใช่แค่รูปแบบที่สวยกว่า
Veedrac

4
คุณต้องใช้ช่วง (1, n + 1) เพื่อหลีกเลี่ยงการหารด้วยศูนย์ นอกจากนี้คุณต้องใช้ float (n) สำหรับการหารแรกหากใช้ Python 2.7 ที่นี่ 1/2 = 0
Jens Munk

-1

สำหรับฉันมันใช้งานได้ดีและสะอาดด้วย (Python 3)

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

ไม่เร็วมาก แต่ส่งกลับตัวหารทีละบรรทัดตามที่คุณต้องการและคุณสามารถทำ list.append (n) และ list.append (number) ได้หากคุณต้องการจริงๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.