What is the most efficient way to find amicable numbers in python?

梦想的初衷 提交于 2019-12-01 08:28:46

optimized to O(n)

def sum_factors(n):  
     result = []
     for i in xrange(1, int(n**0.5) + 1):
         if n % i == 0:
             result.extend([i, n//i])
     return sum(set(result)-set([n]))

def amicable_pair(number):
    result = []
    for x in xrange(1,number+1):
        y = sum_factors(x)
        if sum_factors(y) == x and x != y:
            result.append(tuple(sorted((x,y))))
    return set(result)

run it

start = time.time()
print (amicable_pair(10000))
print time.time()-start

result

set([(2620, 2924), (220, 284), (6232, 6368), (1184, 1210), (5020, 5564)])
0.180204153061

takes only 0.2 seconds on macbook pro

Muhammad Tahir

Lets break down the code and improve the parts of code that is taking so much time.

1-

If you replace if amicable(m, n) == True and m != n: with if m != n and amicable(m, n) == True:, it will save you 10000 calls to amicable method (the most expensive method) for which m != n will be false.

2- In the amicable method you are looping 1 to n to find all the factors for both of the numbers. You need a better algorithm to find the factors. You can use the one mentioned here. It will reduce your O(n) complexity to O(sqrt(n)) for finding factors.

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

Considering both the points above your code will be

def amicable(a, b):
    if sum(factors(a) - {a}) == b and sum(factors(b) - {b}) == a:
        return True
    return False

sum_of_amicables = 0
for m in range (1, 10001):
    for n in range (1, 10001):
        if m!= n and amicable(m, n) == True:
            sum_of_amicables = sum_of_amicables + m + n

This final code took 10 minutes to run for me, which is half the time you have mentioned.


I was further able to optimize it to 1:30 minutes by optimizing factors method.

There are 10000 * 10000 calls to factors method. And factors is called for each number 10000 times. That is, it calculates factors 10000 times for the same number. So we can optimize it by caching the results of previous factors calculation instead of calculating them at every call.

Here is how I modified factors to cache the results.

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

Full Code: (Runtime 1:30 minutes)

So the full and final code becomes

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

def amicable(a, b):
    if sum(factors(a) - {a}) == b and sum(factors(b) - {b}) == a:
        return True
    return False

sum_of_amicables = 0
for m in range (1, 10001):
    for n in range (1, 10001):
        if m!= n and amicable(m, n) == True:
            sum_of_amicables = sum_of_amicables + m + n

You can still further improve it.

Hint: sum is also called 10000 times for each number.

Note that you don't need to have a double loop. Just loop M from 1 to 10000, factorize each M and calculate sum of divisors: S(M). Then check that N = S(M)-M has the same sum of divisors. This is a straight-forward algorithm derived from the definition of an amicable pair.

There are a lot of further tricks to optimize amicable pairs search. It's possible to find all amicable numbers below 1,000,000,000 in just a fraction of a second. Read this in-depth article, you can also check reference C++ code from that article.

Adding to the answer:

def sum_factors(self, n):  
    s = 1
    for i in range(2, int(math.sqrt(n))+1):
        if n % i == 0:
            s += i
            s += n/i
    return s

def amicable_pair(self, number):
    result = 0
    for x in range(1,number+1):
        y = self.sum_factors(x)
        if self.sum_factors(y) == x and x != y:
            result += x
    return result

No need for sets or arrays. Improvinging storage and clarity.

#fetching two numbers from the user
num1=int(input("Enter first number"));
num2=int(input("enter the second number"));
fact1=[];
fact2=[];
factsum1=0;
factsum2=0;


#finding the factors of the both numbers
for i in range(1,num1):
    if(num1%i==0):
        fact1.append(i)


for j in range(1,num2):
    if(num2%j==0):
        fact2.append(j)

print ("factors of {} is {}".format(num1,fact1));
print ("factors of {} is {}".format(num2,fact2));
#add the elements in the list
for k in range(len(fact1)):
    factsum1=factsum1+fact1[k]

for l in range(len(fact2)):
    factsum2=factsum2+fact2[l]

print (factsum1);
print (factsum2);

#compare them
if(factsum1==num2 and factsum2==num1 ):
    print "both are amicable";
else:
    print "not amicable ";








this is my owm understanding of the concept

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!