วิธีที่มีประสิทธิภาพมากที่สุดในการค้นหาปัจจัยทั้งหมดของตัวเลขใน Python คืออะไร?


142

บางคนสามารถอธิบายวิธีที่มีประสิทธิภาพในการค้นหาปัจจัยทั้งหมดของตัวเลขใน Python (2.7) ให้ฉันได้ไหม

ฉันสามารถสร้างอัลกอริทึมเพื่อทำสิ่งนี้ แต่ฉันคิดว่ามันเป็นรหัสที่ไม่ดีและใช้เวลานานเกินไปในการสร้างผลลัพธ์สำหรับจำนวนมาก


3
ฉันไม่รู้จักหลาม แต่หน้านี้อาจมีประโยชน์สำหรับคุณen.wikipedia.org/wiki/Integer_factorization
Stan

3
วิธีการเกี่ยวกับการใช้งานprimefac? pypi.python.org/pypi/primefac
Zubo

คำตอบ:


265
from functools import reduce

def factors(n):    
    return set(reduce(list.__add__, 
                ([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))

สิ่งนี้จะคืนค่าปัจจัยทั้งหมดอย่างรวดเร็วเป็นจำนวนnมาก

ทำไมรากที่สองเป็นขีด จำกัด บน?

sqrt(x) * sqrt(x) = x. ดังนั้นหากทั้งสองปัจจัยเหมือนกันพวกเขาทั้งคู่จะได้สแควร์รูท หากคุณทำให้ปัจจัยใหญ่ขึ้นคุณต้องทำให้ปัจจัยอื่น ๆ มีขนาดเล็กลง ซึ่งหมายความว่าหนึ่งในสองนั้นจะน้อยกว่าหรือเท่ากับsqrt(x)เสมอดังนั้นคุณจะต้องค้นหาจนถึงจุดนั้นเพื่อค้นหาหนึ่งในสองปัจจัยที่ตรงกัน จากนั้นคุณสามารถใช้ที่จะได้รับx / fac1fac2

นี่reduce(list.__add__, ...)คือรายการเล็ก ๆ น้อย ๆ[fac1, fac2]และเข้าร่วมด้วยกันในรายการยาว ๆ รายการเดียว

[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0ผลตอบแทนที่คู่ของปัจจัยถ้าเหลือเมื่อคุณแบ่งnโดยหนึ่งมีขนาดเล็กลงเป็นศูนย์ (มันไม่จำเป็นต้องตรวจสอบขนาดใหญ่หนึ่งมากเกินไปมันก็รับว่าโดยการหารnโดยมีขนาดเล็กหนึ่ง.)

set(...)ด้านนอกคือการกำจัดของรายการที่ซ้ำกันซึ่งจะเกิดขึ้นสำหรับสี่เหลี่ยมที่สมบูรณ์แบบ สำหรับn = 4สิ่งนี้จะกลับมา2สองครั้งดังนั้นsetกำจัดหนึ่งในนั้น


1
ฉันคัดลอกมาจากรายการอัลกอริทึมในคอมพิวเตอร์ของฉันทั้งหมดที่ฉันทำคือแค็ปซูลsqrt- อาจเป็นจากก่อนที่ผู้คนจะคิดถึงการสนับสนุน Python 3 จริงๆฉันคิดว่าไซต์ที่ฉันได้รับจากการทดลอง__iadd__และเร็วขึ้น . ฉันดูเหมือนจะจำบางสิ่งบางอย่างเกี่ยวกับx**0.5การเป็นเร็วกว่าsqrt(x)ในบางจุด - และมันจะเข้าใจผิดได้มากกว่านั้น
agf

7
ดูเหมือนว่าจะทำงานได้เร็วขึ้น 15% ถ้าฉันใช้if not n % iแทนif n % i == 0
dansalmo

3
@sthzg เราอยากให้มันกลับมาเป็นจำนวนเต็มไม่ลอยและงูหลาม 3 /จะกลับลอยแม้ว่าข้อโต้แย้งทั้งสองเป็นจำนวนเต็มและพวกเขาจะ divisable ตรงคือไม่ได้4 / 2 == 2.0 2
agf

7
ฉันรู้ว่านี่เป็นคำถามเก่า แต่ใน Python 3.x คุณต้องเพิ่มfrom functools import reduceเพื่อให้งานนี้
anonymoose

5
@unseen_rider: นั่นไม่ถูกต้อง คุณสามารถให้อะไรก็ได้สำรอง
Ry-

55

โซลูชันที่นำเสนอโดย @agf นั้นยอดเยี่ยม แต่หนึ่งสามารถบรรลุเวลารันได้เร็วขึ้น 50% สำหรับเลขคี่โดยพลการโดยตรวจสอบความเท่าเทียมกัน เนื่องจากปัจจัยของเลขคี่มักเป็นเลขคี่เองจึงไม่จำเป็นต้องตรวจสอบสิ่งเหล่านี้เมื่อต้องจัดการกับเลขคี่

ฉันเพิ่งเริ่มไขปริศนาProject Euler ด้วยตัวเอง ในบางปัญหาการตรวจสอบตัวหารจะเรียกว่าภายในสองforลูปซ้อนกันและประสิทธิภาพของฟังก์ชั่นนี้จึงจำเป็น

เมื่อรวมความจริงนี้เข้ากับโซลูชันที่ยอดเยี่ยมของ agf ฉันได้สิ้นสุดลงด้วยฟังก์ชั่นนี้

from math import sqrt
def factors(n):
        step = 2 if n%2 else 1
        return set(reduce(list.__add__,
                    ([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))

อย่างไรก็ตามสำหรับตัวเลขขนาดเล็ก (~ <100) ค่าใช้จ่ายพิเศษจากการเปลี่ยนแปลงนี้อาจทำให้ฟังก์ชันใช้เวลานานขึ้น

ฉันทำการทดสอบเพื่อตรวจสอบความเร็ว ด้านล่างเป็นรหัสที่ใช้ เพื่อสร้างแปลงที่แตกต่างกันฉันเปลี่ยนX = range(1,100,1)ตาม

import timeit
from math import sqrt
from matplotlib.pyplot import plot, legend, show

def factors_1(n):
    step = 2 if n%2 else 1
    return set(reduce(list.__add__,
                ([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))

def factors_2(n):
    return set(reduce(list.__add__,
                ([i, n//i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0)))

X = range(1,100000,1000)
Y = []
for i in X:
    f_1 = timeit.timeit('factors_1({})'.format(i), setup='from __main__ import factors_1', number=10000)
    f_2 = timeit.timeit('factors_2({})'.format(i), setup='from __main__ import factors_2', number=10000)
    Y.append(f_1/f_2)
plot(X,Y, label='Running time with/without parity check')
legend()
show()

X = ช่วง (1,100,1) X = ช่วง (1,100,1)

ไม่มีความแตกต่างอย่างมีนัยสำคัญที่นี่ แต่ด้วยจำนวนที่มากขึ้นข้อดีก็ชัดเจน:

X = ช่วง (1,100000,1000) (เฉพาะเลขคี่) X = ช่วง (1,100000,1000) (เฉพาะเลขคี่)

X = ช่วง (2,100000,100) (เฉพาะเลขคู่) X = ช่วง (2,100000,100) (เฉพาะเลขคู่)

X = ช่วง (1,100000,1001) (สลับพาริตี้) X = ช่วง (1,100000,1001) (สลับพาริตี้)


28

คำตอบของ agf นั้นยอดเยี่ยมจริงๆ reduce()ผมอยากจะดูว่าฉันสามารถเขียนมันหลีกเลี่ยงการใช้ นี่คือสิ่งที่ฉันมาด้วย:

import itertools
flatten_iter = itertools.chain.from_iterable
def factors(n):
    return set(flatten_iter((i, n//i) 
                for i in range(1, int(n**0.5)+1) if n % i == 0))

ฉันยังลองรุ่นที่ใช้ฟังก์ชันตัวสร้างที่ซับซ้อน:

def factors(n):
    return set(x for tup in ([i, n//i] 
                for i in range(1, int(n**0.5)+1) if n % i == 0) for x in tup)

ฉันหมดเวลาโดยการคำนวณ:

start = 10000000
end = start + 40000
for n in range(start, end):
    factors(n)

ฉันวิ่งไปหนึ่งครั้งเพื่อให้ไพ ธ อนรวบรวมจากนั้นก็วิ่งตามคำสั่ง (1) สามครั้งและเก็บเวลาที่ดีที่สุดไว้

  • ลดรุ่น: 11.58 วินาที
  • รุ่น itertools: 11.49 วินาที
  • รุ่นหากิน: 11.12 วินาที

โปรดทราบว่ารุ่น itertools กำลังสร้างสิ่งอันดับและส่งผ่านไปยัง flatten_iter () ถ้าฉันเปลี่ยนรหัสเพื่อสร้างรายการแทนมันจะช้าลงเล็กน้อย:

  • รุ่น iterools (รายการ): 11.62 วินาที

ฉันเชื่อว่ารุ่นฟังก์ชั่นเครื่องกำเนิดไฟฟ้าที่ยุ่งยากนั้นเป็นไปได้เร็วที่สุดใน Python แต่มันไม่เร็วกว่ารุ่นลดจริง ๆ เร็วกว่าประมาณ 4% จากการวัดของฉัน


2
คุณสามารถทำให้ "รุ่นที่ยุ่งยาก" ง่ายขึ้น (ลบที่ไม่จำเป็นออกfor tup in):factors = lambda n: {f for i in range(1, int(n**0.5)+1) if n % i == 0 for f in [i, n//i]}
jfs

11

ทางเลือกอื่นในการตอบ agf:

def factors(n):    
    result = set()
    for i in range(1, int(n ** 0.5) + 1):
        div, mod = divmod(n, i)
        if mod == 0:
            result |= {i, div}
    return result

1
คุณช่วยอธิบาย div, mod part ได้ไหม?
Adnan

3
divmod (x, y) ส่งคืน ((xx% y) / y, x% y) คือความหารและส่วนที่เหลือของการหาร
c4757p

สิ่งนี้ไม่สามารถจัดการกับปัจจัยที่ซ้ำซ้อนได้ดีตัวอย่างเช่นลอง 81
phkahler

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

ฉันซ้อนทุกอย่างไว้ในบรรทัดเดียวเพราะคำตอบของ agf ทำเช่นนั้น ฉันสนใจที่จะดูว่าreduce()เร็วกว่าอย่างมากหรือไม่ดังนั้นฉันจึงทำทุกอย่างนอกเหนือจากreduce()ส่วนที่เป็นวิธีเดียวกับที่ agf ทำ สำหรับการอ่านก็จะดีที่จะเห็นการเรียกฟังก์ชันเช่นแทนที่จะแสดงออกเช่นis_even(n) n % 2 == 0
steveha

9

นี่เป็นอีกทางเลือกหนึ่งสำหรับโซลูชันของ @ agf ซึ่งใช้อัลกอริทึมแบบเดียวกันในสไตล์ pythonic ที่มากขึ้น:

def factors(n):
    return set(
        factor for i in range(1, int(n**0.5) + 1) if n % i == 0
        for factor in (i, n//i)
    )

โซลูชันนี้ใช้ได้ทั้ง Python 2 และ Python 3 ที่ไม่มีการนำเข้าและสามารถอ่านได้มากขึ้น ฉันไม่ได้ทดสอบประสิทธิภาพของวิธีการนี้ แต่ไม่แสดงอาการแบบ asymptotically และหากประสิทธิภาพเป็นสิ่งที่น่ากังวลอย่างยิ่ง


7

มีอัลกอริทึมความแข็งแกร่งของอุตสาหกรรมใน SymPy เรียกว่าfactorint :

>>> from sympy import factorint
>>> factorint(2**70 + 3**80) 
{5: 2,
 41: 1,
 101: 1,
 181: 1,
 821: 1,
 1597: 1,
 5393: 1,
 27188665321L: 1,
 41030818561L: 1}

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

ด้วยปัจจัยสำคัญทั้งหมดทำให้สามารถสร้างปัจจัยอื่น ๆ ได้อย่างง่ายดาย


โปรดทราบว่าแม้ว่าคำตอบที่ได้รับอนุญาตจะได้รับอนุญาตให้ทำงานได้นานพอ (เช่นนิรันดร์) เพื่อแยกแยะตัวเลขด้านบน แต่สำหรับจำนวนที่มากมันอาจจะล้มเหลวเช่นตัวอย่างต่อไปนี้ int(n**0.5)นี่คือสาเหตุที่เลอะเทอะ ตัวอย่างเช่นเมื่อn = 10000000000000079**2เรามี

>>> int(n**0.5)
10000000000000078L

เนื่องจาก1000000000000007979 เป็นสิ่งที่ดีที่สุดอัลกอริทึมของคำตอบที่ยอมรับจะไม่พบปัจจัยนี้ โปรดทราบว่ามันไม่ได้เป็นเพียงการปิดโดยหนึ่ง สำหรับตัวเลขขนาดใหญ่มันจะถูกปิดโดยมากขึ้น ด้วยเหตุนี้จึงเป็นการดีกว่าที่จะหลีกเลี่ยงตัวเลขทศนิยมในอัลกอริทึมของการจัดเรียงนี้


2
ไม่พบตัวหารทั้งหมด แต่เป็นเพียงปัจจัยสำคัญเท่านั้นจึงไม่ใช่คำตอบจริงๆ คุณควรแสดงให้เห็นว่าปัจจัยอื่น ๆ สามารถสร้างได้อย่างไรไม่ใช่แค่พูดง่าย ๆ ! โดยวิธีการ sympy.divisors อาจจะตรงกับที่ดีกว่าที่จะตอบคำถามนี้
โคลิน Pitrat

และทราบว่า sympy.divisors นั้นไม่เร็วกว่าโซลูชันที่ยอมรับ
โคลิน Pitrat

@ColinPitrat: ฉันสงสัยว่าsympy.divisorsมันไม่เร็วขึ้นมากสำหรับตัวเลขที่มีตัวหารน้อยโดยเฉพาะ มีเกณฑ์มาตรฐานหรือไม่?
Ry-

@Ry ฉันทำเมื่อฉันเขียนความคิดเห็นนี้เมื่อปีที่แล้ว ใช้เวลา 2 นาทีในการเขียนดังนั้นอย่าลังเลที่จะตรวจสอบอีกครั้ง
โคลินพิตรัต

3
@ColinPitrat: ตรวจสอบแล้ว ตามที่คาดไว้คำตอบที่ยอมรับนั้นมีความเร็วเท่ากับsympy.divisors100,000 และช้ากว่าสำหรับทุกอย่างที่สูงกว่า (และแน่นอนว่าสามารถsympy.divisorsทำงานกับตัวเลข10000000000000079**2ได้)
Ry-

7

สำหรับ n มากถึง 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))

6

ปรับปรุงเพิ่มเติมเพื่อแก้ปัญหาของ afg & eryksun ชิ้นส่วนของรหัสต่อไปนี้จะส่งกลับรายการที่เรียงลำดับของปัจจัยทั้งหมดโดยไม่ต้องเปลี่ยนความซับซ้อนเชิงเส้นกำกับเวลา:

    def factors(n):    
        l1, l2 = [], []
        for i in range(1, int(n ** 0.5) + 1):
            q,r = n//i, n%i     # Alter: divmod() fn can be used.
            if r == 0:
                l1.append(i) 
                l2.append(q)    # q's obtained are decreasing.
        if l1[-1] == l2[-1]:    # To avoid duplication of the possible factor sqrt(n)
            l1.pop()
        l2.reverse()
        return l1 + l2

แนวคิด: แทนที่จะใช้ฟังก์ชั่น list.sort () เพื่อรับรายการเรียงลำดับซึ่งให้ความซับซ้อนของ nlog (n) มันเร็วกว่าการใช้ list.reverse () บน l2 ซึ่งใช้ O (n) ซับซ้อน (นั่นเป็นวิธีการที่ไพ ธ อนทำ) หลังจาก l2.reverse (), l2 อาจถูกผนวกเข้ากับ l1 เพื่อรับรายการเรียงลำดับของปัจจัย

ประกาศ l1 ประกอบด้วยi -s ซึ่งกำลังเพิ่มขึ้น l2 ประกอบด้วยq -s ซึ่งกำลังลดลง นั่นคือเหตุผลที่อยู่เบื้องหลังการใช้ความคิดข้างต้น


list.reverseค่อนข้างแน่ใจว่าO (n) ไม่ใช่ O (1) ไม่ใช่ว่ามันจะเปลี่ยนความซับซ้อนโดยรวม
agf

ใช่มันเป็นสิ่งที่ถูก. ฉันทำผิดพลาด ควรเป็น O (n) (ฉันได้อัปเดตคำตอบตอนนี้เป็นคำตอบที่ถูกต้องแล้ว)
Pranjal Mittal

ช้ากว่าโซลูชั่นของ @ steveha หรือ @ agf ประมาณ 2 เท่า
jfs

คุณสามารถรับความเร็วที่เพิ่มขึ้นเล็กน้อย (2-3%) โดยกลับมาl1 + l2.reversed()แทนที่การย้อนกลับรายการ
Rakurai

6

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

def factors(n):
    results = set()
    for i in xrange(1, int(math.sqrt(n)) + 1):
        if n % i == 0:
            results.add(i)
            results.add(int(n/i))
    return results

ตามที่เขียนไว้คุณจะต้องนำเข้าคณิตศาสตร์เพื่อทดสอบ แต่แทนที่ math.sqrt (n) ด้วย n ** 5 ควรใช้งานได้เช่นกัน ฉันไม่ต้องเสียเวลาตรวจสอบรายการซ้ำเพราะรายการซ้ำไม่สามารถอยู่ในชุดได้


สิ่งที่ยอดเยี่ยม! หากคุณใส่ int (math.sqrt (n)) + 1 ด้านนอกของ for loop คุณควรจะได้ประสิทธิภาพที่เพิ่มขึ้นอีกเล็กน้อยเนื่องจากไม่จำเป็นต้องคำนวณซ้ำอีกครั้งสำหรับการวนรอบ
Tristan Forward

3
@TristanForward: นั่นไม่ใช่วิธีการทำงานของลูปใน Python xrange(1, int(math.sqrt(n)) + 1)ถูกประเมินครั้งเดียว
Ry-

5

นี่เป็นอีกทางเลือกหนึ่งโดยไม่มีการลดที่ทำงานได้ดีกับจำนวนมาก มันใช้sumในการแผ่รายการ

def factors(n):
    return set(sum([[i, n//i] for i in xrange(1, int(n**0.5)+1) if not n%i], []))

1
นี่ไม่ได้เป็นเวลากำลังสองที่ไม่จำเป็น อย่าใช้sumหรือreduce(list.__add__)ทำให้รายการเรียบ
juanpa.arrivillaga

4

ให้แน่ใจว่าจะคว้าหมายเลขที่มีขนาดใหญ่กว่าsqrt(number_to_factor)สำหรับตัวเลขที่ผิดปกติเช่น 99 ซึ่งมี 3 * 3 * floor sqrt(99)+1 == 1011

import math

def factor(x):
  if x == 0 or x == 1:
    return None
  res = []
  for i in range(2,int(math.floor(math.sqrt(x)+1))):
    while x % i == 0:
      x /= i
      res.append(i)
  if x != 1: # Unusual numbers
    res.append(x)
  return res

1
มันไม่ได้สร้างปัจจัยทั้งหมดของตัวเลข มันคำนวณปัจจัยสำคัญของตัวเลขเช่นสำหรับที่x=8คาดไว้: [1, 2, 4, 8]ได้รับ:[2, 2, 2]
jfs

11 พบเมื่อ 9 ถูก comupted ในรหัสที่กำหนดโดย @agf `i = 9 -> 99% 9 == 0 -> 9 และ 99/9 = 11 ถูกเพิ่ม
Steinar Lima

4

วิธีที่ง่ายที่สุดในการค้นหาปัจจัยของตัวเลข:

def factors(x):
    return [i for i in range(1,x+1) if x%i==0]

2

นี่คือตัวอย่างถ้าคุณต้องการใช้หมายเลขช่วงเวลาเพื่อให้เร็วขึ้นมาก รายการเหล่านี้หาได้ง่ายบนอินเทอร์เน็ต ฉันเพิ่มความคิดเห็นในรหัส

# http://primes.utm.edu/lists/small/10000.txt
# First 10000 primes

_PRIMES = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 
        31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
        73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 
        127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 
        179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 
        233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
        283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 
        353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 
        419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 
        467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 
        547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 
        607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 
        661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 
        739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 
        811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 
        877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 
        947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 
# Mising a lot of primes for the purpose of the example
)


from bisect import bisect_left as _bisect_left
from math import sqrt as _sqrt


def get_factors(n):
    assert isinstance(n, int), "n must be an integer."
    assert n > 0, "n must be greather than zero."
    limit = pow(_PRIMES[-1], 2)
    assert n <= limit, "n is greather then the limit of {0}".format(limit)
    result = set((1, n))
    root = int(_sqrt(n))
    primes = [t for t in get_primes_smaller_than(root + 1) if not n % t]
    result.update(primes)  # Add all the primes factors less or equal to root square
    for t in primes:
        result.update(get_factors(n/t))  # Add all the factors associted for the primes by using the same process
    return sorted(result)


def get_primes_smaller_than(n):
    return _PRIMES[:_bisect_left(_PRIMES, n)]

ฉันสร้างโครงการ Github A: github.com/Pierre-Thibault/Factor
ปิแอร์ธีโบลท์

2

อัลกอริทึมที่อาจเกิดขึ้นได้อย่างมีประสิทธิภาพมากขึ้นกว่าคนที่นำเสนอที่นี่แล้ว (โดยเฉพาะอย่างยิ่งถ้ามี factons นายกเล็ก ๆ ในn) เคล็ดลับที่นี่คือการปรับขีด จำกัด ที่ต้องการแผนกการทดลองทุกครั้งที่พบปัจจัยสำคัญ:

def factors(n):
    '''
    return prime factors and multiplicity of n
    n = p0^e0 * p1^e1 * ... * pk^ek encoded as
    res = [(p0, e0), (p1, e1), ..., (pk, ek)]
    '''

    res = []

    # get rid of all the factors of 2 using bit shifts
    mult = 0
    while not n & 1:
        mult += 1
        n >>= 1
    if mult != 0:
        res.append((2, mult))

    limit = round(sqrt(n))
    test_prime = 3
    while test_prime <= limit:
        mult = 0
        while n % test_prime == 0:
            mult += 1
            n //= test_prime
        if mult != 0:
            res.append((test_prime, mult))
            if n == 1:              # only useful if ek >= 3 (ek: multiplicity
                break               # of the last prime) 
            limit = round(sqrt(n))  # adjust the limit
        test_prime += 2             # will often not be prime...
    if n != 1:
        res.append((n, 1))
    return res

แน่นอนว่านี่ยังคงเป็นการแบ่งกลุ่มทดลองใช้และไม่มีอะไรแฟนซีมากขึ้น และยังคงมีข้อ จำกัด ในด้านประสิทธิภาพ (โดยเฉพาะอย่างยิ่งสำหรับคนตัวใหญ่ที่ไม่มีตัวหารขนาดเล็ก)

นี่คือ python3; การแบ่ง//ควรเป็นสิ่งเดียวที่คุณต้องปรับสำหรับ python 2 (เพิ่มfrom __future__ import division)


1

การใช้set(...)ทำให้โค้ดช้าลงเล็กน้อยและจำเป็นจริงๆเมื่อคุณตรวจสอบรากที่สอง นี่คือรุ่นของฉัน:

def factors(num):
    if (num == 1 or num == 0):
        return []
    f = [1]
    sq = int(math.sqrt(num))
    for i in range(2, sq):
        if num % i == 0:
            f.append(i)
            f.append(num/i)
    if sq > 1 and num % sq == 0:
        f.append(sq)
        if sq*sq != num:
            f.append(num/sq)
    return f

if sq*sq != num:สภาพเป็นสิ่งที่จำเป็นสำหรับตัวเลขเช่น 12 ที่รากที่ไม่ได้เป็นจำนวนเต็ม แต่พื้นของรากที่เป็นปัจจัย

โปรดทราบว่ารุ่นนี้ไม่ส่งคืนหมายเลข แต่เป็นวิธีแก้ไขที่ง่ายหากคุณต้องการ เอาท์พุทยังไม่ได้จัดเรียง

ฉันจับเวลามันวิ่ง 10,000 ครั้งกับตัวเลขทั้งหมด 1-200 และ 100 ครั้งในทุกหมายเลข 1-5000 มันมีประสิทธิภาพสูงกว่ารุ่นอื่น ๆ ทั้งหมดที่ฉันทดสอบรวมถึง dansalmo's, Jason Schorn's, oxrock's, agf's, steveha's และโซลูชั่นของ eryksun แม้ว่า oxrock จะใกล้เคียงที่สุด


1

ค่าสูงสุดของคุณไม่เกินจำนวนของคุณดังนั้นสมมุติว่า

def factors(n):
    factors = []
    for i in range(1, n//2+1):
        if n % i == 0:
            factors.append (i)
    factors.append(n)

    return factors

! Voila


1
 import math

    '''
    I applied finding prime factorization to solve this. (Trial Division)
    It's not complicated
    '''


    def generate_factors(n):
        lower_bound_check = int(math.sqrt(n))  # determine lowest bound divisor range [16 = 4]
        factors = set()  # store factors
        for divisors in range(1, lower_bound_check + 1):  # loop [1 .. 4]
            if n % divisors == 0:
                factors.add(divisors)  # lower bound divisor is found 16 [ 1, 2, 4]
                factors.add(n // divisors)  # get upper divisor from lower [ 16 / 1 = 16, 16 / 2 = 8, 16 / 4 = 4]
        return factors  # [1, 2, 4, 8 16]


    print(generate_factors(12)) # {1, 2, 3, 4, 6, 12} -> pycharm output

 Pierre Vriens hopefully this makes more sense. this is an O(nlogn) solution. 

0

ใช้บางอย่างง่าย ๆ ตามรายการความเข้าใจโดยสังเกตว่าเราไม่จำเป็นต้องทดสอบ 1 และหมายเลขที่เราพยายามค้นหา:

def factors(n):
    return [x for x in range(2, n//2+1) if n%x == 0]

ในการอ้างอิงถึงการใช้สแควร์รูทบอกว่าเราต้องการค้นหาปัจจัย 10 ส่วนที่เป็นจำนวนเต็มของsqrt(10) = 4ดังนั้นจึงrange(1, int(sqrt(10))) = [1, 2, 3, 4]ทดสอบได้ถึง 4 พลาด 5 อย่างชัดเจน

int(ceil(sqrt(x)))เว้นแต่ฉันไม่มีอะไรที่ผมจะแนะนำถ้าคุณต้องทำมันด้วยวิธีนี้ใช้ หลักสูตรนี้ทำให้เกิดการเรียกใช้ฟังก์ชันที่ไม่จำเป็นจำนวนมาก


ปัญหาของการแก้ปัญหานี้คือการตรวจสอบตัวเลขจำนวนมากที่ไม่สามารถเป็นปัจจัยได้ - และมันจะตรวจสอบแต่ละปัจจัยคู่ที่สูงกว่าแยกกันเมื่อคุณรู้แล้วว่ามันเป็นปัจจัยหลังจากหาตัวประกอบของคู่ที่เล็กลง
agf

1
@ JasonSchorn: เมื่อคุณพบ 2 คุณจะรู้ทันทีว่า 10/2 = 5 เป็นตัวหารเช่นกันไม่จำเป็นต้องตรวจสอบ 5 แยกต่างหาก! :)
Moberg

0

ฉันคิดว่าการอ่านง่ายและ speed @ ทางออกของ oxrock นั้นดีที่สุดดังนั้นนี่คือโค้ดที่เขียนใหม่สำหรับ python 3+:

def num_factors(n):
    results = set()
    for i in range(1, int(n**0.5) + 1):
        if n % i == 0: results.update([i,int(n/i)])
    return results

0

ฉันค่อนข้างประหลาดใจเมื่อเห็นคำถามนี้ว่าไม่มีใครใช้ numpy แม้ว่า numpy จะเร็วกว่า python loops โดยการดำเนินการแก้ปัญหา @ AGF กับ numpy และจะเปิดออกเฉลี่ย8x ได้เร็วขึ้น ฉันเชื่อว่าถ้าคุณนำวิธีการแก้ปัญหาอื่น ๆ มาใช้ในจำนวนมากคุณจะได้รับช่วงเวลาที่น่าอัศจรรย์

นี่คือหน้าที่ของฉัน:

import numpy as np
def b(n):
    r = np.arange(1, int(n ** 0.5) + 1)
    x = r[np.mod(n, r) == 0]
    return set(np.concatenate((x, n / x), axis=None))   

ขอให้สังเกตว่าตัวเลขของแกน x ไม่ใช่อินพุตของฟังก์ชั่น อินพุตของฟังก์ชั่นคือ 2 ต่อจำนวนบนแกน x ลบ 1 ดังนั้นที่สิบคืออินพุตจะเป็น 2 ** 10-1 = 1023

ผลการทดสอบประสิทธิภาพของการใช้ numpy แทนที่จะเป็นลูป


1
หากคุณกำลังจะใช้ห้องสมุดอาจทำให้มันเป็นหนึ่งที่เหมาะสม: SymPy ดังที่เห็นในคำตอบของ Evgeni Sergeev
Ry-

0
import 'dart:math';
generateFactorsOfN(N){
  //determine lowest bound divisor range
  final lowerBoundCheck = sqrt(N).toInt();
  var factors = Set<int>(); //stores factors
  /**
   * Lets take 16:
   * 4 = sqrt(16)
   * start from 1 ...  4 inclusive
   * check mod 16 % 1 == 0?  set[1, (16 / 1)]
   * check mod 16 % 2 == 0?  set[1, (16 / 1) , 2 , (16 / 2)]
   * check mod 16 % 3 == 0?  set[1, (16 / 1) , 2 , (16 / 2)] -> unchanged
   * check mod 16 % 4 == 0?  set[1, (16 / 1) , 2 , (16 / 2), 4, (16 / 4)]
   *
   *  ******************* set is used to remove duplicate
   *  ******************* case 4 and (16 / 4) both equal to 4
   *  return factor set<int>.. this isn't ordered
   */

  for(var divisor = 1; divisor <= lowerBoundCheck; divisor++){
    if(N % divisor == 0){
      factors.add(divisor);
      factors.add(N ~/ divisor); // ~/ integer division 
    }
  }
  return factors;
}

อัลกอริทึมเกือบทั้งหมดที่นี่ จำกัด ช่วงเป็นตัวเลข * .5 แต่จริงๆแล้วช่วงนั้นเล็กกว่ามาก จำนวนจริงของ sqrt ถ้าเรามีตัวหารที่ต่ำกว่าเราก็สามารถทำให้เขาได้อันดับหนึ่งได้อย่างง่ายดาย เนื่องจากมันเป็นเพียงตัวเลข / ตัวหาร สำหรับ 16 ฉันได้ 4 สำหรับ sqrt แล้ววนจาก 1 เป็น 4 เนื่องจาก 2 คือตัวหารที่มีขอบเขตล่างของ 16 เราใช้ 16/2 เพื่อรับ 8 ถ้าเรามี 1 แล้วถึงได้ 16 คือ (16/1) ฉันคิดขึ้นมาในขณะที่เรียนรู้เกี่ยวกับการแยกตัวประกอบเฉพาะดังนั้นฉันไม่รู้ว่ามันถูกเผยแพร่ที่อื่น แต่มันใช้ได้แม้กับคนจำนวนมาก ฉันสามารถให้วิธีแก้ปัญหาหลาม
Tangang Atanga

-4

ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุดในการทำเช่นนั้น:

    x = 23

    i = 1
    while i <= x:
      if x % i == 0:
        print("factor: %s"% i)
      i += 1

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