A Foray into Rust
: Euler One
2021-07-31 | 2023-11-26
Estimated Reading Time: 15 minutes
As a programmer, I am long in the tooth. I started out with FORTRAN
, went on to Forth
, and settled with
C
through three decades or more. Later, it was MATLAB
and Octave
for high level computing. For scripting, I used Perl
or bash
. Python
, the current
darling of programmers, is an unknown bourne to
me.
So why did I choose Rust
as the new
programming language to learn? Rust is the emerging programming
language, developed at Mozilla [1]. It has been consistently voted the
most loved programming language in Stack Overflow Developer Surveys
[2]. End-users,
such as scientists,
are turning to Rust when Python has proven inadequate for some reason
[3]. And corporate users
include Dropbox, Mozilla, Microsoft, npm, etc. [4].
But there are other, more personal, reasons as well. My previous bet
on the future was on Haskell
. I have tried
many times to learn it, almost always giving up in despair, because I
was put off by the unfamiliar notation, and its corpus of arcana, like
monads,
touted by the cognoscenti, as the way to tell the men from the boys.
Enough about the why. Now for the how.
I decided to start learning Rust
by solving Project Euler Problem
Oneโhenceforth called Euler One, the problem, or
the questionโusing Rust. This is a chronicle of my first
efforts, including false starts, errors, backtracks, etc.
Project Euler Problem One
The statement of the problem is simple and pellucid:
Multiples of 3 or 5
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. [Emphasis is mine]
Algorithm for problem solving
The algorithm for problem solving is [5]:
- Read the question carefully.
- Understand the question correctly.
- Answer the question precisely.
The problem asks for all the multiples of
Parsing the question
The word โorโ
I have emphasized three words in the problem definition: all, or, and below. The first is obvious. Let us look at the other two.
The phrase โmultiples of 3 or 5โ may be interpreted in two
ways. If we think of it as an inclusive or, then it means
โmultiples of
If we think of it as an exclusive or, then it means
โmultiples of
Since the qualification of โbut not bothโ is absent from the rubric, we will assume an inclusive or, i.e., the first interpretation.
The word โbelowโ
The word โbelowโ introduces the mathematical relation
The time spent in looking at the question through a magnifying glass is time well spent, because it forces us to assume the mindset of the author who carefully crafted the question. We thereby become acquainted with the possibilities for pitfalls and potholes that could otherwise upend our efforts.
Initial thoughts
The multiples of
In terms of division, the %
operator for integer
division from other programming languages suggests itself. But is
division the most natural way to identify the multiples of a number?
Should it not be multiplication instead? It is time to start thinking
with a beginnerโs
mind.
We also need a structure like an array or list
where numbers may be appended or inserted until the stopping condition
is reached. If we keep a running total, though, we do not need anything
else except three receptacles: one for the sum of multiples of three,
Setting the bounds
We know that
Likewise,
With
Because
Venn diagram representation
Viewing a problem pictorially often helps us to grasp it better. In
this case, it is not a graph but a Venn
diagram that helps. In Figure 1, we use
circles A and B to represent the sets of multiples of
We know from set
theory that what we are after is
Algorithm
The most direct algorithm to solve the problem in pseudocode is:
Define
as the cumulative sum of the multiples of๐ 3 , and initialize it to3 .0 Define
as the cumulative sum of the multiples of๐ 5 , and initialize it to5 .0 Define
as the cumulative sum of multiples of๐ 1 5 , and initialize it to1 5 .0 Loop through the natural numbers
fromโ to1 , compute the multiples of3 3 3 , one at a time, and add it to3 .๐ 3 Loop through the natural numbers
fromโ to1 , compute the multiples of1 9 9 , one at a time, and add it to5 .๐ 5 Loop through the natural numbers
fromโ to1 , compute the multiples of6 6 , one at a time, and add it to1 5 .๐ 1 5 Evaluate
and present it as the desired result. See Equation 1 for an explanation.( ๐ 3 + ๐ 5 โ ๐ 1 5 )
Pseudocode
I envisage three independent for
loops to achieve this.
The pseudocode could read:
s3 = s5 = s15 = 0 # initialize variables
for n in [1:333]
do
s3 = s3 + 3*n
done
for n i [1:199]
do
s5 = s5 + 5*n
done
for n i [1:66]
do
s15 = s15 + 15*n
done
print (s3 + s5 - s15)
We have implicitly assumed that the for
loop increment
is
First attempt
Let us barge ahead using the syntax of Rust
and see how
the above pseudo code fleshes out. It turns out that Rust
supports five types of loop and we need the one with the
for
flavour, called the iterator loop.
There is also an example on that web page that is similar to our
problem. It uses a for
loop, but the variable holding the
sum is initialized using the mut
keyword. Let us copy the code fragment and change it to suit our
purposes:
// Attempt Number 1
let mut s3 = 0;
let mut s5 = 0;
let mut s15 = 0;
for n in 1..333 {
+= n*3;
s3 }
for n in 1..199 {
+= n*5;
s5 }
for n in 1..66 {
+= n*15;
s15 }
+ s5 - s15); println(s3
Not surprisingly, the above fragment contains numerous errors and
would not compile. So, I needed to backtrack to see an example of the
archetypal โHello World!โ program to get the proper
invocatory syntax. Languages like C
and
Java
come with some baggage that needs to be wrapped around
the core code so that it may be rendered into an executable program.
Rust
seems to have borrowed this characteristic from them.
Note the use of s3 += n*3;
which is shorthand for
s3 = s3 + n*3
. The +=
operator is available in
Rust
, but not always in other languages.
Second attempt
My second attempt at the program, with proper indentation, is now:
// Attempt Number 2
fn main() {
let mut s3 = 0;
let mut s5 = 0;
let mut s15 = 0;
for n in 1..333 {
+= n*3;
s3 }
for n in 1..199 {
+= n*5;
s5 }
for n in 1..66 {
+= n*15;
s15 }
println!("{}", s3 + s5 - s15);
}
I have wrapped the whole code fragment with a main()
function just as in C
. Moreover, I have learned that
println!
is a macro rather than a function and that it is
invoked as shown. This has already disheartened me a bit because
something too much like C
or Java
โwith a lot
of clunky statements for simple actionsโis a step in the wrong
direction for an easier-to-use programming language. Let us hope it does
not rain pickaxes and shovels when we compile the code!
This time, the code was compiled without a murmur. Upon execution,
the answer was
Result with Octave
The easiest and laziest way to check the result was to use a
naturally vector-based language to verify the above result. I chose
Octave
as it is freely available, and I know it somewhat.
Because the natural data structure in Octave
is a vector or
a matrix, I could type out the whole sequence using the syntax
[start:step:end]
and sum it up to get the three sums of
multiples. The code was so easy, that I could write it without reference
to paper:
sum([3:3:999]) + sum([5:5:995]) - sum([15:15:990])
and this gave a result of Rust
. I must also say that, though laconic,
Octave
got the job done with very little fuss or fanfare.
Vectorized code is both more powerful and simpler to understand and
maintain. The best language for someone working with vectors is one that
supports them natively.
We must now make a third, โrepair and maintenanceโ attempt with the
rust
code.
Troubleshooting
The rust
program is so simple that the most likely error
must lie with the limits in the for
loop. Indeed, an
experienced programmer would have seen it at once.
Programming languages are notoriously inconsistent on two fronts:
- Whether they start their indexing with
or with0 ; and1 - Whether their index ranges are on closed intervals
, or semi-closed intervals[ ๐ , ๐ ] , or[ ๐ , ๐ ) , or open intervals( ๐ , ๐ ] ( ๐ , ๐ ) .
One would have thought that common sense would impel language designers to adopt uniform conventions on these two issues. Unfortunately the authors of programming languages have rather fiercely held philosophical notions, and a divide persists. Thus each foray into a new language must be cautiously done with these two factors in mind.
In our case, we need to hark back to the definition
of the ..
range operator in Rust
. The
expression start..end
means that the index variable
i
lies in a semi-closed interval:
start <= i < end
. The end
parameters in
each case need to be increased by one in our program. Our third attempt
is shown below:
Third attempt
// Attempt Number 3
fn main() {
let mut s3 = 0;
let mut s5 = 0;
let mut s15 = 0;
for n in 1..334 {
+= n*3;
s3 }
for n in 1..200 {
+= n*5;
s5 }
for n in 1..67 {
+= n*15;
s15 }
println!("{}", s3 + s5 - s15);
}
This again complied incident-free and the result that popped out was
Octave
gave us. That is a reassuring feeling. The real
arbiter of truth, though, is mathematics. What does it say?
The Gold Standard
We are fortunate that in this case, the mathematics is both simple
and well known. We are dealing with the sums of three arithmetic
progressions (AP). The first term in an AP is usually
denoted
The required sum,
Vectorizing
The single-line Octave
program made the solution seem
laughably easy. Why? Because the standard data structure in
Octave
is a vector or a matrix. In the context of
Rust
, we may pose these questions:
Does
Rust
have a ready implementation of vectors that may be called upon?Would such an implementation be faster? Less error prone? Easier to visualize and troubleshoot?
I had a little peep at the possibilities with Rust
and
realized that being a multi-paradigm
language, Rust
provides many possibilities to
accomplish the same task. And the choices available will overwhelm a
Rust-neophyte like me. Moreover, once the simplicity of scalars is left
behind, the knowledge curve with vectors is rather steep. So,
vectorizing must promise returns commensurate with the learning effort.
I will leave vectors in Rust
for another day.
FizzBuzz
The FizzBuzz coding problem is a natural successor to EulerOne. The original problem, used in early school to teach multiplication, is stated for coding below:
For every integer from
to 1 , print Fizz if it is divisible by ๐ , Buzz if it is divisible by 3 , and FizzBuzz if it is divisible by 5 . Otherwise, do nothing. [For our purposes, we may set an upper limit as 1 5 .] ๐ < 1 0 0 0
This is a favourite coding-interview problem because it is simple enough to reveal the thought processes of the candidate who wrote the program. Note that we are asked to sort the numbers into four groups.
Vectors and set intersections are the easiest way to achieve this,
but Rust
presents a steep climb in knowledge acquisition
before even meagre results start trickling in.
With Euler One, we have already computed the sums of multiples of
Octave
implementation of FizzBuzz
In Octave
, the implementation of FizzBuzz
is starkly simple. The availability of the set
difference as an operation gives us a ready-made solution as shown
below. Of course, I have not printed the output, but the vectors named
fizz
, buzz
and fizzbuzz
contain
the numbers whose elements are associated with these responses.1 This is more a โproof-of-conceptโ
demonstration, rather than a proper solution, because the logic
associating the response with the number is missing.
% FizzBuzz
= [3:3:999];
threes = [5:5:995];
fives = [15:15:990];
fifteens = setdiff (threes, fifteens);
fizz = setdiff (fives, fifteens);
buzz = fifteens; fizzbuzz
Closing thoughts
To learn Rust
requires fortitude of mind and heart. It
is not for the timid. It is no swimming-pool language; it plumbs the
ocean deeps. Its power must lie in its apparent versatility. I do not
feel any heart-tug to learn it when Ocatve
, like Aladdinโs
Lamp, is there to fulfil my programming wishes. But for those who are
professional programmers, I think that missing out on Rust
might be like missing out on the main course in a meal.
Afterword
After I had written this blog, I came across a fascinating blog on Euler One [6].
He has made the very valid point that whenever we are faced with
products of a constant
I urge you to read the blog to stretch your mental muscles, while at the same time developing an appreciation for the beauty of mathematics. Try your own hand at analyzing the seemingly simple Euler One problem and see whether it gives you insights that you did not have before.
Caveat Lector! or Reader Beware! or Disclaimer
I am learning Rust
. What I have written here represents
my efforts at learning. The code here is not mature, idiomatic
Rust
code and should not be construed as such. Do not take
it as an example of how to code in Rust
. Experienced
โRustaceansโ who find errors are requested to email me with their
corrections. ๐
Feedback
Please email me your comments and corrections.
A PDF version of this article is available for download here: