8
\$\begingroup\$

I'm a couple weeks into learning Python and I'm working through the Project Euler problems. Problem 12 asks:

What is the value of the first triangle number to have over five hundred divisors?

My problem is that my code takes far too long.

I had a list in there to store the factors but I replaced it with a counter variable to count the number of factors. It resets after looping through the divisors.

I'm not entirely sure but I feel like there is a much better way to cycle through the divisors?

How could I optimise this piece of code?

target = 100000000
counter = 0
for value in xrange(1, target + 1):
 x = (value * (value + 1))/2
 for divisor in xrange(1, x+1):
 product = x % divisor
 if product == 0:
 counter += 1
 if counter >= 500:
 print x
 sys.exit()
 else:
 counter = 0

I appreciate that similar questions have already been asked but I am hoping to gain useful 'personalised' advice from this community

Please excuse the vague variable names and the code in general...

Martin R
24.2k2 gold badges37 silver badges95 bronze badges
asked Jul 24, 2017 at 16:06
\$\endgroup\$
2
  • 1
    \$\begingroup\$ See here \$\endgroup\$ Commented Jul 24, 2017 at 17:43
  • 2
    \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers . \$\endgroup\$ Commented Jul 24, 2017 at 19:23

1 Answer 1

7
\$\begingroup\$

Iteration, flow control, naming, and clarity

Your iteration is a bit clumsy.

It seems that target is an arbitrarily chosen number that you picked just to act as a sufficiently large second parameter to xrange(). If you want an unbounded counting loop, itertools.count(1) would be better.

Instead of else: counter = 0, you should just set counter = 0 in the right place — just before the inner loop.

Avoid using sys.exit() — it's like killing your program with a sledgehammer. You should structure your code so that it terminates naturally.

from itertools import count
for value in count(1):
 x = (value * (value + 1))/2
 for divisor in xrange(1, x+1):
 product = x % divisor
 if product == 0:
 counter += 1
 if counter >= 500:
 break
print x

There is an idiom for counting items that meet some criterion: sum() with a generator expression.

from itertools import count
for value in count(1):
 x = (value * (value + 1)) / 2
 if 500 <= sum(1 for divisor in xrange(1, x + 1) if x % divisor == 0):
 print x
 break

As you noted yourself, your variables are poorly named. product, in particular, should be called remainder. counter is vague; divisor_count would be more helpful.

What you want to express is a loop over the triangle numbers. For even greater clarity, I'd break that out into a triangle number generator function.

from itertools import count
def triangle_numbers():
 """Generator of triangle numbers, starting with 1, 1+2, 1+2+3, ..."""
 n = 0
 for i in count(1):
 n += i
 yield n
def divisor_count(n):
 """Count of the divisors of n"""
 return sum(1 for divisor in xrange(1, n + 1) if n % divisor == 0)
print next(t for t in triangle_numbers() if divisor_count(t) >= 500)

Mathematics

Note that \$\dfrac{n(n+1)}{2}\$ is a product of two coprimes. As explained here, the divisors of such a product can be computed based on the divisors of each of its two known factors.

from itertools import count
def divisor_count(n):
 """Count of the divisors of n"""
 return sum(1 for divisor in xrange(1, n + 1) if n % divisor == 0)
for n in count(1):
 tn = n * (n + 1) // 2
 # n and (n + 1) are relatively prime. Therefore, if n is even,
 # the factors of tn can be derived from the factors of n/2 and
 # the factors of n+1. If n is odd, then the factors of tn can be
 # derived from the factors of n and the factors of ((n+1)/2).
 tn_divisor_count = (
 divisor_count(n // 2) * divisor_count(n + 1) if n % 2 == 0 else
 divisor_count(n) * divisor_count((n + 1) // 2)
 )
 if tn_divisor_count >= 500:
 print tn
 break
answered Jul 24, 2017 at 17:49
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Good answer, but for a beginner, a generator function might be a bit much. \$\endgroup\$ Commented Jul 24, 2017 at 18:19

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.