I've become interested prime factorization since solving Project Euler problem 3 (finding the largest prime factor of 600851475143). Learning here that initializing lists with many elements to later prune them is the computational equivalent of swimming with bricks, I challenged myself to code a function that returns all prime factors as efficiently as possible, without the need for initializing a big list beforehand.
The routine takes the input n and divides it by 2 as many times as possible before leaving a remainder, redefining n to be the result of the division each time. When 2 no longer cleanly divides n, it moves to 3, but it is after 3 that the crux of the inefficiency arises.
After 3, if n does not equal 1 and there are still factors to be found, the next factor tried is the next consecutive odd integer after 3. For example, take the number \$129373200 = (2^4) × (3^5) × (5^2) × (11^3)\$. After the algorithm divides all 2's, 3's and 5's away, the next test-number is 7, which I view as efficient because 7 is prime. However, as the routine iterates, 9 is tested before 11, and I see this as inefficient because 9 is composite.
If I edit the code such that it stores all tested numbers in a list, and checks if the next test-number is a multiple of a previously tested one or not before iterating, the runtime slows down considerably. Is there an efficient way to do this sort of check via elementary functions without storing tested numbers in a list (or set, dictionary, etc.)?
TLDR: I want to understand why factoring numbers with 8-digit-long prime factors takes the code 7 seconds versus numbers with 9-digit-long prime factors that take nearly 2.5 minutes to solve. I want to reduce this large jump in runtime.
def pf(n):
startTime=datetime.now()
factors = [] #Initialize a list to store prime factors.
while n % 2 == 0: #While n/2 continues to yield no remainder:
factors += [2] #Append 2 to the factor list.
n /= 2 #Redefine n as n/2.
if n == 1: #If n is 1, all of its prime factors have been found,
print datetime.now() - startTime
return factors #so return the factor list to the user.
p = 3 #Initialize a count at 3, the next prime after 2
while p*p <= n: #While n is greater than or equal to p*p:
if n%p == 0: #If p divides n:
factors += [p] #Append p to the factor list.
n /= p #Redefine n as n/p.
else: #If p doesn't divide n:
p += 2 #See if the next consecutive odd number up divides n.
#Once all smaller factors are found, and n is smaller than p*p,
factors += [n] #append n to the factor list,
print datetime.now() - startTime,
return factors #and then return it to the user.
print pf(121)
print pf(42768)
print pf(19440)
print pf(97200)
print pf(129373200) #inefficiency example
print pf(600851475143)
print pf(31610054640417607788145206291543662493274686990) #consecutive primes
print pf(4383898882371133212190175441147530134182228613257) #5-6 digit primes
print pf(815145012617325671714771027149) #8-digit primes
print pf(9657874875862260078751562987967607300225789) #9-digit primes
Output:
0:00:00 [11, 11] 0:00:00 [2, 2, 2, 2, 3, 3, 3, 3, 3, 11] 0:00:00 [2, 2, 2, 2, 3, 3, 3, 3, 3, 5] 0:00:00 [2, 2, 2, 2, 3, 3, 3, 3, 3, 5, 5] 0:00:00 [2, 2, 2, 2, 3, 3, 3, 3, 3, 5, 5, 11, 11, 11] 0:00:00 [71, 839, 1471, 6857L] 0:00:00 [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, 113L] 0:00:00.023000 [37489, 59617, 63577, 63841, 74771, 75521, 82217, 97283, 102181, 104717L] 0:00:06.935000 [15485867, 32452843, 32452867, 49979687L] 0:02:24.362000 [122949829, 314606891, 393342743, 674506111, 941083987L]