If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
Permalink: http://projecteuler.net/problem=1
The simplest problem and nothing hard to solve it in standard iterative way (C-style pseudocode):
int s = 0;
for (int i=0; i < 1000; i++) {
if ((i % 3 == 0) || (i % 5 == 0)) s+=i;
}
We get result immediately. But loop that changes value of some variable it’s not a functional approach. Let’s rewrite this solution in recursive way in clojure:
(loop [i 0 s 0]
(if (= i 1000) s
(if (or (= 0 (mod i 3))
(= 0 (mod i 5))) (recur (inc i) (+ s i))
(recur (inc i) s))))
Just few words what we are doing here:
i
and s
along with calling such function with arguments i=0, s=0
.
i
is a loop counter, s
is an accumulator variable that holds current sum.(+ i 1)
.i
value equals to 1000
, then we finished our work and return current accumulator value s
. Otherwise,i
value is multiply of 3
or multiply of 5
, we add counter value to accumulator,
increment loop counter and call this function with these new arguments. Otherwise, we just increment value and call function.
This way we recursively go back to the first line of function with changed values until we get recursion base condition as true and return accumulator.
Recursion call performed by recur.This can be a bit harder to understand than iterative style, but we have a couple of pros there.
Congratulations! Problem solved, but let’s find more elegant way.
We can split our problem into two simpler problems:
3
below 1000
5
below 1000
Stop, stop! What about numbers that multiples of 3
and multiples of 5
, like 15, 30, 45,...
? We add them twice.
Good catch. Let’s just subtract them once from total sum:
3
below 1000
5
below 1000
15
below 1000
You see the repeated “Find sum of all…“. It’s clear definition of function. Let’s implement it.
(defn sum-of [d] (reduce + (range 0 1000 d)))
One line! Excited? Brief description, what we wrote:
sum-of
, it accepts one argument d
(multiple of what?).
As you, probably notice, we do not enforced to specify type of function argument, as well as function returning type.
All it’s because clojure is dynamic language.
Dynamic languages have pros and cons over static, but we omit their differences. For now.(reduce * [2 3 4 5])
calculates (* 2 3)
that is 6
, calls (reduce * [6 4 5])
, then calls (reduce * [24 5])
and finally returns 120
.(range 0 1000 d)
start generation from 0
,
next value will be previous plus d
, until we exceed 1000
. Exclusively. So, (range 0 10 3)
returns (0 3 6 9)
.Ok. We have function “Find sum of all…” and almost all work is done. Just call this function with proper arguments and you’ll get the result:
(+ (sum-of 3) (sum-of 5) (- (sum-of 15)))
Done. In functional, clear, concise way in two lines of code.
Note: our algorithm complexity is O(n)
.
If we remember school course of math, we realize that all our sequences are arithmetic progressions.
That means for caluclating sum of sequence we don’t need count sum in usual way, we just can use formula:
n
is a count of numbers in a sequence, a1
- first value, an
- last value.
Now we can reimplement our sum-of
function next way:
(defn sum-of [d]
(let [a1 d
an (- 999 (mod 999 d))
n (quot 999 d)]
(/ (* cnt (+ a1 an)) 2)))
More harder to understand, but doable. New language syntax appears (999 used as we have “below 1000” in problem description):
a1
- first element of sequence, just step value d
.an
- last element of sequence. We don’t know last element, so we need to calculate it.
Little trick: find the remainder of division 999 by d, and subtract it from 999.n
- count of numbers in a sequence. We also don’t know this value. It just will be integer number of division 999 by d.
Exactly what quot function does.Actually, we reduced readability of our code, but improved algorithmic complexity from O(n)
to O(1)
.
GitHub for lazy.
P.S. Programming not tied to some problem is nothing. That’s why, in first problem I prefer O(n)
solution.
It still solves problem in appropriate time and have more readable code. If n
grow, I’ll think about switching to O(1)
strategy, but not for now.
Have fun!
mishadoff 12 October 2012