3
p = []
for x in range(1, 50000000):
    count = 0
    for y in range(1, x // 2 + 1):
        if (x % y == 0):
            count += y
    if (count == x):
        p.append(x)

This is my code to try and find all the perfect numbers that originate between 1 and 50000000. It works fine for the first 3 numbers, they are between 1 and 10000. But as it progresses it becomes extremely slow. Like maybe going through 1000 numbers every 10 seconds. Then eventually going through 10 numbers every 5 seconds.

Now is there anyway I could make this faster? I tried including some smaller things, like diving x by 2 to make sure we don't go over half (not going to be a multiple of x)

Reece
  • 41
  • 4
  • I think you shouldn't do the brute-force way to solve this mathy problem, I'd recommend you to read more about Mersenne primes and then you'll understand your approach isn't convenient at all :) – BPL Nov 16 '16 at 11:03

3 Answers3

2

As has already been mentioned, no odd perfect numbers have been found. And according to the Wikipedia article on perfect numbers, if any odd perfect numbers exist they must be greater than 101500. Clearly, looking for odd perfect numbers takes sophisticated methods &/or a lot of time. :)

As stated on Wikipedia, Euclid proved that even perfect numbers can be produced from Mersenne primes, and Euler proved that, conversely, all even perfect numbers have that form.

So we can produce a list of even perfect numbers by generating Mersenne primes. And we can (relatively) quickly test if a number is a Mersenne prime via the Lucas-Lehmer test.

Here's a short program that does just that. The primes function used here was written by Robert William Hanks, the rest of the code was just written by me a few minutes ago. :)

''' Mersenne primes and perfect numbers '''

def primes(n):
    """ Return a list of primes < n """
    # From http://stackoverflow.com/a/3035188/4014959
    sieve = [True] * (n//2)
    for i in range(3, int(n**0.5) + 1, 2):
        if sieve[i//2]:
            sieve[i*i//2::i] = [False] * ((n - i*i - 1) // (2*i) + 1)
    return [2] + [2*i + 1 for i in range(1, n//2) if sieve[i]]

def lucas_lehmer(p):
    ''' The Lucas-Lehmer primality test for Mersenne primes.
        See https://en.wikipedia.org/wiki/Mersenne_prime#Searching_for_Mersenne_primes
    '''
    m = (1 << p) - 1
    s = 4
    for i in range(p - 2):
        s = (s * s - 2) % m
    return s == 0 and m or 0

#Lucas-Lehmer doesn't work on 2 because it's even, so we just print it manually :)
print('1 2\n3\n6\n')
count = 1
for p in primes(1280):
    m = lucas_lehmer(p)
    if m:
        count += 1
        n = m << (p - 1)
        print(count, p)
        print(m)
        print(n, '\n')

output

1 2
3
6

2 3
7
28 

3 5
31
496 

4 7
127
8128 

5 13
8191
33550336 

6 17
131071
8589869056 

7 19
524287
137438691328 

8 31
2147483647
2305843008139952128 

9 61
2305843009213693951
2658455991569831744654692615953842176 

10 89
618970019642690137449562111
191561942608236107294793378084303638130997321548169216 

11 107
162259276829213363391578010288127
13164036458569648337239753460458722910223472318386943117783728128 

12 127
170141183460469231731687303715884105727
14474011154664524427946373126085988481573677491474835889066354349131199152128 

13 521
6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
23562723457267347065789548996709904988477547858392600710143027597506337283178622239730365539602600561360255566462503270175052892578043215543382498428777152427010394496918664028644534128033831439790236838624033171435922356643219703101720713163527487298747400647801939587165936401087419375649057918549492160555646976 

14 607
531137992816767098689588206552468627329593117727031923199444138200403559860852242739162502265229285668889329486246501015346579337652707239409519978766587351943831270835393219031728127
141053783706712069063207958086063189881486743514715667838838675999954867742652380114104193329037690251561950568709829327164087724366370087116731268159313652487450652439805877296207297446723295166658228846926807786652870188920867879451478364569313922060370695064736073572378695176473055266826253284886383715072974324463835300053138429460296575143368065570759537328128 

15 1279
10407932194664399081925240327364085538615262247266704805319112350403608059673360298012239441732324184842421613954281007791383566248323464908139906605677320762924129509389220345773183349661583550472959420547689811211693677147548478866962501384438260291732348885311160828538416585028255604666224831890918801847068222203140521026698435488732958028878050869736186900714720710555703168729087
54162526284365847412654465374391316140856490539031695784603920818387206994158534859198999921056719921919057390080263646159280013827605439746262788903057303445505827028395139475207769044924431494861729435113126280837904930462740681717960465867348720992572190569465545299629919823431031092624244463547789635441481391719816441605586788092147886677321398756661624714551726964302217554281784254817319611951659855553573937788923405146222324506715979193757372820860878214322052227584537552897476256179395176624426314480313446935085203657584798247536021172880403783048602873621259313789994900336673941503747224966984028240806042108690077670395259231894666273615212775603535764707952250173858305171028603021234896647851363949928904973292145107505979911456221519899345764984291328 

That output was produced in 4.5 seconds on a 2GHz 32 bit machine. You can easily produce larger Mersenne primes and perfect numbers, but don't expect it to be fast.

PM 2Ring
  • 50,023
  • 5
  • 64
  • 150
1

You can do a lot better. Consider the following:

1) Think of the factorization of 36 for example: 1x36, 2x18, 3x12, 4x9, 6x6. And that's it. Going further doesn't add anything new. The next factorization would be 9x4 but we already know that (4x9). This advantage gets progressively larger (compare the root of the last number you have to check against its half)

2) There are no odd perfect numbers. This is actually a conjecture (not proven yet) but they tried everything below 10^300 and didn't find any. So there are definately exactly none < 50000000. That means you can skip half the terms.

from math import ceil
p = []
for x in range(2, 50000000, 2):
    divisors = {1}
    for y in range(2, ceil(x**0.5) + 1):
        if x % y == 0:
            divisors.update({y, (x//y)})
    if sum(divisors) == x:
        print('-', x)
        p.append(x)
#- 6
#- 28
#- 496
#- 8128

This should be a lot faster but there are definately more things one can do.

Ma0
  • 14,004
  • 2
  • 29
  • 59
  • 4
    If you're to skip odd numbers, you might as well take advantage of the Euclid-Euler theorem and just compute `2^(k-1)·(2^k - 1)`. – spectras Nov 16 '16 at 11:03
  • I would have but I am having lunch! Thanks for reminding me though!! – Ma0 Nov 16 '16 at 11:04
  • Indeed, as @spectras suggest this is far away to be the right solution to this mathy problem – BPL Nov 16 '16 at 11:04
  • It should be "x // 2 + 1" not "ceil(x**0.5)):" – Reece Nov 16 '16 at 11:08
  • @Reece You can't be serious. – Stefan Pochmann Nov 16 '16 at 11:12
  • @Reece> `**` operator in python is exponentiation. Thus `x**0.5` is _√x_. — Stefan, be nice with newcomers, especially ones that can put together a decent question *and* don't loose interest as soon as they get an answer ;) – spectras Nov 16 '16 at 11:48
  • @Reece I am sorry, the reason it was not running properly was that i was adding `x+y` to the counter when i should have been adding `y + (x//y)`. Notice that the counter has been initialised to `1` instead of 0 since the *1xn* factorzation can be skipped. – Ma0 Nov 16 '16 at 12:21
  • @StefanPochmann It was the `range(2, ceil(x**0.5))`. It has to be changed to `range(2, ceil(x**0.5) + 1)` to include the root of `x` when it is an integer. All number of the n^2 form had the same problem. – Ma0 Nov 16 '16 at 14:44
  • @StefanPochmann isn't that how it is supposed to be? That's how i understand this statement *"a perfect number is a positive integer that is equal to the sum of its proper positive divisors, that is, the sum of its positive divisors excluding the number itself"* – Ma0 Nov 16 '16 at 14:53
  • @StefanPochmann Now every divisor is computed once. '6' is also in the list. – Ma0 Nov 16 '16 at 14:58
  • Yeah, now it looks ok. And I agree that using a set makes it nicer (and probably not much slower). – Stefan Pochmann Nov 16 '16 at 15:06
0

Here's a solution using some precomputed values of Mersenne Primes:

mersenne_prime_powers = [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127]

def perfect_numbers(n=10):
    return [(2**p - 1) * (2**(p - 1)) for p in mersenne_prime_powers[:n]]

print(perfect_numbers(n=5))

Output:

[6, 28, 496, 8128, 33550336]
BPL
  • 9,807
  • 7
  • 37
  • 90
  • 1
    Here's an even simpler one that makes about as much sense: `return [6, 28, 496, 8128, 33550336]`. – Stefan Pochmann Nov 16 '16 at 11:14
  • @StefanPochmann It's quite clear from that comment "you didn't understand at all my answer..." , one clue though, analize "Mersenne Primes generation" complexity and then look back at OP's original algorithm :) . Also, I'd recommend you to take a look to "Today's number" [here](http://www.mersenne.org/), that's pretty much ;) – BPL Nov 16 '16 at 11:19
  • 1
    I think it's you who doesn't understand. My point is that if you're going to look up and use something you couldn't come up with yourself, you might as well look up the thing that you're after. I see no advantage in your way. – Stefan Pochmann Nov 16 '16 at 11:24
  • @StefanPochmann I do understand the complexity of calculating "Mersenne Primes", coming up with a python algorithm to derive a general solution to the "First n-perfect numbers" problem is just nonsense in a single CPU, if you understand how complex is calculating mersenne primes you'll see my way is one of the best choices to search "Perfect numbers". Following your logic, would you also code an algorithm to compute ASCII codes? any other LUT? it's just nonsense... anyway, i won't put more effort on changing your mind, from your first comment i see it's pointless. Peace – BPL Nov 16 '16 at 11:29
  • 2
    Well, to be fair it looks like a homework assignment, and sense comes not from the results but from the thoughts process. I'd even argue implementing the naive solution and seeing how fast it slows to a crawl is an experience in itself when learning to deal with this class of problems. – spectras Nov 16 '16 at 11:47
  • @spectras Indeed, it looks like a homework assignment, that's why If I was the teacher I'd like my students to understand the underlying maths (mersenne primes in this particular one) before even thinking about optimizing the wrong approach. Brute-force approach on maths is usually not the right way to go... just saying. as a comparison: let's say we have ax^2+bx+c=0, are you going to loop from [-K,K] checking every single input or just using the general solution? . On the other hand, I agree with you about getting real experience with these type of problems, makes sense – BPL Nov 16 '16 at 11:57
  • I tend to agree with what you're saying, BPL. If you're going to play around with number theory it's kinda useful to actually _know_ some number theory. :) However, it's possible to explain the concept of perfect numbers to a student who isn't yet ready for Euclid's proof. OTOH, Euclid's proof isn't _that_ hard to follow. But proving the converse _is_ a little trickier. – PM 2Ring Nov 16 '16 at 12:19
  • 1
    @PM2Ring Indeed indeed :) . That applies for all mathy levels, in fact, usually when I start solving "medium/hard" Euler projects I tend to start with the naive obvious brute-force approach and optimizing from there, until I realize I won't go further with more clever optimizations, when I reach that point of awareness I just start learning more about the necessary maths to solve the problem "the right way". Anyway, my answer was just an attempt to motivate OP's to learn more about "Mersenne Primes", I didn't intend to optimize its loop at all, I think that'd mislead the learning path – BPL Nov 16 '16 at 12:43