A more beautiful loop
Starting from this piece of code, we'll start going for a more elegant implementation:
i = 0
...
while i < len ( multiples ):
for k in range (start, stop ):
if (k % multiples[i]) == 0:
sums.append(k)
else:
continue
i = i + 1 # increments through the list values by 1
We could start by shaving off the un-necessary: the blank lines and the else-continue
.
We can also bring the i = 0
part closer to the block actually using it.
Also, the comment # increments through the list values by 1
does not bring much information. Anytime you add a comment, you can ask yourself "could this actually help anyone reading the code ?". A common rule of thumb is to document the "why" rather than the "how": tell what you want/need to do and why rather than how you do it.
Finally, the i = i + 1
can be written i += 1
.
At this stage, we have:
i = 0
while i < len ( multiples ):
for k in range (start, stop ):
if (k % multiples[i]) == 0:
sums.append(k)
i += 1
Then, things can still be improved. For a start, you call the len
function at each iteration. It's not a very expensive operation but it is still un-necessary. However, the actual part to change is to switch the while
loop for a for
loop: your situation looks like the typical situation to use a for
loop. Using the range
function you already know:
for i in range(len(multiples)):
for k in range (start, stop ):
if (k % multiples[i]) == 0:
sums.append(k)
Please note that it removed the need to initialise i
, to increment it after each iteration and the len
function is only called once. This looks like the perfect solution, doesn't it ?
Not quite! This loop uses an index to go through elements of a container. This is how most people (used to) do in various programming languages. However Python defines objects that are iterable and a clean syntax to use them. You can simply write for num in multiples
. This is very well explained by Ned Batchelder's talk "loop like a native"
for mul in multiples:
for k in range(start, stop):
if (k % mul) == 0:
sums.append(k)
Also, this could be written using a list comprehension, but I'd rather avoid making things too complicated for now.
Handling duplicated values
You have different ways to handle duplicated values with pros and cons. At the moment, you put all values in a list then convert it to a set. A. Romeu's answer suggest checking if the value is in the container before adding it (which can be inefficient because then, you'd need to iterate over the whole list before adding a new value to know it is not there yet).
Graipher's comment suggested using a set directly. This is a pretty good solution. You'd have something like:
sums = set()
for mul in multiples:
for k in range(start, stop):
if (k % mul) == 0:
sums.add(k)
Not much to change :-)
For the sake of learning new things, we could go for a different approach. What if the best way to handle duplicates was not to have duplicates in the first place. At the moment, your code iterate over range(start, stop)
many times. Changing the code a bit, we could iterate over it only once.
sums = []
for k in range(start, stop):
# TODO: if a number in multiples divides k
sums.append(k)
This could be:
sums = []
for k in range(start, stop):
for mul in multiples:
if k % mul == 0:
sums.append(k)
break
Making your code testable
The Project Euler problem gives you an example of input and expected output. You could take this chance to write tests to ensure that your code is valid regarding this example. Also, this would lead to writing smaller, easier to reuse and to understand function. We could imagine writing something like: def sum_of_multiples(limit, multiples)
and checking that sum_of_multiples(10, [3, 5]) == 23
.
Mathematical optimisation
It doesn't really matter on this problem but the further you go into Project Euler problems, the less it becomes about writing a straight-forward bruteforce solution and the more it becomes about finding mathematical properties to be able to write an efficient solution.
On this problem, there is already a much more satisfying solution to mathematicians. The key insight is that we have formulas to compute the num of number from i
to j
. This can easily be used to compute the sum of multiples of 3
or 5
between i
and j
. Then, it leads to the number you're looking for (note: multiples of 15
are counted twice but you can once again compute their sum easily).