patternpythonModerate
Finding number pairs where the sum of their divisors are each other
Viewed 0 times
sumnumbertheeacharewheredivisorsfindingotherpairs
Problem
Given a limit \$n\$, find all the pairs of values less than, or equal to n, which have the the property:
I need to speed up my double for-loop but I don't see how to do it in this case.
pair(a,b)
where a = sum of divisors of b
and b = sum of divisors of aI need to speed up my double for-loop but I don't see how to do it in this case.
def diviseurs_propres(n):
t = [1]
for i in range(2,n):
if n%i == 0:
t.extend([i])
return t
def somme_diviseurs_propres(n):
t = diviseurs_propres(n)
S = sum(list(t))
return S
def trouveur(n):
t = []
for i in range(0,n+1):
for p in range(0,n+1):
if p != i and somme_diviseurs_propres(i) == p and i == somme_diviseurs_propres(p):
t.extend([p]) and t.extend([i])
return tSolution
Mike is correct that you are repeating the sum multiple times for each value. A simple array with the stored sum will make a huge difference.
On the other hand, you are also doing a huge amount of unnecessary work in here:
There is no need to store the actual values in an array, it can be simplified to just:
With a little bit of math, you can significantly reduce the number of iterations by iterating to the square-root of the value:
The way the above code works, is each time you have a value \$a\$ that is a factor of \$n\$, then there must be a value \$n / a\$ that is also an factor.
Thus, for large values of \$n\$, like 1000000, you only need to iterate from 2 to 1000 (exclusive) to sum the factors. Now, because 1000000 is an exact square, the root is an actual integer too, and the last if-statement above adds in the value 1000 to the sum.
In addition to the above, you can also rearrange the double-loops so that the inner loop is limited by the outer loop, and then add the pairs twice to the results. This changes the order of the results, but not the actual content.... Finally, your use of
Now, there's a bug in there, I believe. The code:
does not add the second value
Using a dictionary
As an update, I have considered a second soltuion, that should significantly reduce the amount of iteration in the inner loops, at the expense of lookups in a dictionary.
The dictionary maps the sum-of-factors to the values that have that sum. For example, the sum of factors for 6 and 25 are both 6. (sum of factors of 6 are
Using that mapping, we can look for sums that have cross-references... We can loop through just the sums of the factors, and for each sum, in their set of input values, see if those values exist as sums of other values. If they do, then see if the original sum is one of them.... This makes sense if you see an example:
the top value shows that the sum of factors for 284 is 220, and the sum of factors for 220, is 284.
Taking the first value, we know the sum of factors, and we take all the values that sum to that (in this case, just 284), and check whether 284 happens to be the sum of some other value's factors. Then we check to see whether one of those other values is 220.
Performance results:
Your initial code, with a limit of 500
Using Mike's suggestion of saving away the sums:
This produces the times:
Including the changes I suggest
Code:
```
import math
def somme_diviseurs_propres(n):
sum = 1
root = int(math.ceil(math.sqrt(n)))
for i in range(2, root):
if n % i == 0:
sum += i + (n / i)
if root > 1 and root * root == n:
sum += root
return sum
def trouveur(n):
somme = []
t = []
for i in range(0, n + 1):
somme.append(somme_diviseurs_pro
On the other hand, you are also doing a huge amount of unnecessary work in here:
def diviseurs_propres(n):
t = [1]
for i in range(2,n):
if n%i == 0:
t.extend([i])
return t
def somme_diviseurs_propres(n):
t = diviseurs_propres(n)
S = sum(list(t))
return SThere is no need to store the actual values in an array, it can be simplified to just:
def somme_diviseurs_propres(n):
sum = 1
for i in range(2, n):
if n % i == 0:
sum += i
return sumWith a little bit of math, you can significantly reduce the number of iterations by iterating to the square-root of the value:
def somme_diviseurs_propres(n):
sum = 1
root = int(math.ceil(math.sqrt(n)))
for i in range(2, root):
if n % i == 0:
sum += i + (n / i)
if root > 1 and root * root == n:
sum += root
return sumThe way the above code works, is each time you have a value \$a\$ that is a factor of \$n\$, then there must be a value \$n / a\$ that is also an factor.
Thus, for large values of \$n\$, like 1000000, you only need to iterate from 2 to 1000 (exclusive) to sum the factors. Now, because 1000000 is an exact square, the root is an actual integer too, and the last if-statement above adds in the value 1000 to the sum.
In addition to the above, you can also rearrange the double-loops so that the inner loop is limited by the outer loop, and then add the pairs twice to the results. This changes the order of the results, but not the actual content.... Finally, your use of
extend should be replaced with append...def trouveur(n):
t = []
for i in range(0, n+1):
for p in range(0, i):
if somme_diviseurs_propres(i) == p and i == somme_diviseurs_propres(p):
t.append(p) and t.append(i)
t.append(i) and t.append(p)
return tNow, there's a bug in there, I believe. The code:
t.extend([p]) and t.extend([i])does not add the second value
i. You only add the i on the second time through, when i == p and p == i .... Your code above is equivalent to just:t.extend([p])Using a dictionary
As an update, I have considered a second soltuion, that should significantly reduce the amount of iteration in the inner loops, at the expense of lookups in a dictionary.
The dictionary maps the sum-of-factors to the values that have that sum. For example, the sum of factors for 6 and 25 are both 6. (sum of factors of 6 are
3 + 2 + 1, and for 25 it is 5 + 1). The mapping looks like:6 -> [6, 25]Using that mapping, we can look for sums that have cross-references... We can loop through just the sums of the factors, and for each sum, in their set of input values, see if those values exist as sums of other values. If they do, then see if the original sum is one of them.... This makes sense if you see an example:
220 -> [284]
284 -> [220]the top value shows that the sum of factors for 284 is 220, and the sum of factors for 220, is 284.
Taking the first value, we know the sum of factors, and we take all the values that sum to that (in this case, just 284), and check whether 284 happens to be the sum of some other value's factors. Then we check to see whether one of those other values is 220.
def trouveur(n):
sommeMap = {}
for i in range(1, n + 1):
s = somme_diviseurs_propres(i)
if s in sommeMap:
sommeMap[s].append(i)
else:
sommeMap[s] = [i]
t = []
for i,ps in sommeMap.items():
for p in ps:
if i < p and p in sommeMap and i in sommeMap[p]:
t.append(i)
t.append(p)
return tPerformance results:
Your initial code, with a limit of 500
Result: [284, 220]
real 0m4.851s
user 0m4.840s
sys 0m0.012sUsing Mike's suggestion of saving away the sums:
def trouveur(n):
somme = []
t = []
for i in range(0,n+1):
somme.append(somme_diviseurs_propres(i))
for i in range(0,n+1):
for p in range(0,n+1):
if p != i and somme[i] == p and i == somme[p]:
t.extend([p]) and t.extend([i])
return tThis produces the times:
Result: [284, 220]
real 0m0.064s
user 0m0.056s
sys 0m0.008sIncluding the changes I suggest
- no arrays for the sum of factors
- change the loops
- separate the
iandpappends
Code:
```
import math
def somme_diviseurs_propres(n):
sum = 1
root = int(math.ceil(math.sqrt(n)))
for i in range(2, root):
if n % i == 0:
sum += i + (n / i)
if root > 1 and root * root == n:
sum += root
return sum
def trouveur(n):
somme = []
t = []
for i in range(0, n + 1):
somme.append(somme_diviseurs_pro
Code Snippets
def diviseurs_propres(n):
t = [1]
for i in range(2,n):
if n%i == 0:
t.extend([i])
return t
def somme_diviseurs_propres(n):
t = diviseurs_propres(n)
S = sum(list(t))
return Sdef somme_diviseurs_propres(n):
sum = 1
for i in range(2, n):
if n % i == 0:
sum += i
return sumdef somme_diviseurs_propres(n):
sum = 1
root = int(math.ceil(math.sqrt(n)))
for i in range(2, root):
if n % i == 0:
sum += i + (n / i)
if root > 1 and root * root == n:
sum += root
return sumdef trouveur(n):
t = []
for i in range(0, n+1):
for p in range(0, i):
if somme_diviseurs_propres(i) == p and i == somme_diviseurs_propres(p):
t.append(p) and t.append(i)
t.append(i) and t.append(p)
return tt.extend([p]) and t.extend([i])Context
StackExchange Code Review Q#80740, answer score: 16
Revisions (0)
No revisions yet.