6
\$\begingroup\$

I'm starting to learn rust and figured my first simple project would be to solve some of the Euler problems. I'd appreciate if anyone could tell me where I might be going wrong in my setup, language usage, or lack of rusty code.

Note, I'm not looking for feedback on the algorithms or solutions to the problem. My goal isn't to solve the Euler problems in the best and most efficient way, but to learn Rust by solving these problems. Solutions as posed may be sub-optimal.

Here's the general file structure.

src\ |- main.rs |- problems\ |- mod.rs |- p001.rs |- p002.rs ... // more files in the tree 

And the contents of select files.

src/main.rs

mod problems; use std::collections::HashMap; use std::env::args; use std::time::Instant; use std::vec::Vec; fn main() { // Define the list of function pointers to each problem's solution let mut func_map: HashMap<&str, fn()> = HashMap::new(); func_map.insert("1", problems::p001::multiples_of_3_or_5); func_map.insert("2", problems::p002::even_fib_numbers); ... // More functions added here // Get the arguments from the command line. Then check if only one argument // was provided (namely the executable name, indicating the user provided // no arguments). In that case, just set the arguments to all the function // map keys so every single problem is solved. // TODO: This results in the keys being unsorted and the problems are solved // in a random order. It'd be nice to resolve that. let mut args: Vec<String> = args().collect(); if args.len() == 1 { args = func_map.keys().map(|k| String::from(*k)).collect(); } // Get a time at the start of solving all the problems. let start = Instant::now(); // Loop over all the requested problems, one by one, and solve them. for problem in args { match func_map.get(problem.as_str()) { Some(f) => { let problem_start = Instant::now(); f(); println!("Completed in {:?}\n", problem_start.elapsed()); } None => continue, } } println!("Solved all problems in {:?}\n", start.elapsed()); } 

src/problems/mod.rs

pub mod p001; pub mod p002; ... // more modules added here 

src/problems/p001.rs

//=============================== Project Euler ================================ // // Problem 1: 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. // //============================================================================== // Define the end point of values to use in the sum const END: u64 = 1000; // The most efficient algorithm here starts with the numbers 3 and 5 and // increments each by 3 and 5 respectively, adding the results to the running // sum and stopping once 1000 is hit. #[allow(dead_code)] pub fn multiples_of_3_or_5() { println!("Solving Problem 1: Multiples of 3 or 5"); let mut sum: u64 = 0; // Sum of all relevant values // Define our values that will hold the multiples of 3 and 5 let mut mul3: u64 = 3; let mut mul5: u64 = 5; // Loop over all multiples of 3, adding it to the sum each time while mul3 < END { sum += mul3; mul3 += 3; } // Loop over all multiples of 5, adding it to the sum each time, if it isn't // a multiple of 3 already. while mul5 < END { if 0 != mul5 % 3 { sum += mul5 } mul5 += 5; } println!("Sum of multiples of 3 or 5 below 1000 is {}", sum); } // This is a dumb, brute force approach that just loops over all possible numbers // from 1 to 1000, checking each one to see if it is divisible by 3 or 5 and adding // it to the running sum. Because it checks every number, it isn't as efficient as // just adding the numbers we already know are divisible by 3 or 5 (by virtue of // simply getting all the multiples of 3 and 5). #[allow(dead_code)] pub fn multiples_of_3_or_5_brute_force() { println!("Solving Problem 1: Multiples of 3 or 5"); let mut sum: u64 = 0; // Sum of all relevant values for ii in 1..END { if 0 == ii % 3 || 0 == ii % 5 { sum += ii; } } println!("Sum of multiples of 3 or 5 below 1000 is {}", sum); } 

src/problems/p002.rs

//=============================== Project Euler ================================ // // Problem 2: Even Fibonnaci Numbers // // Each new term in the Fibonacci sequence is generated by adding the previous // two terms. By starting with 1 and 2, the first 10 terms will be: // // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... // // By considering the terms in the Fibonacci sequence whose values do not exceed // four million, find the sum of the even-valued terms. // //============================================================================== #[allow(dead_code)] pub fn even_fib_numbers() { println!("Solving Problem 2: Even Fibonacci Numbers"); // Define the initial two terms, along with a third temp variable let mut a: u64 = 1; let mut b: u64 = 2; let mut temp: u64; // Define the variable for the sum and initially set it equal to the first // two terms. let mut sum: u64 = 0; // Start a loop that will calculate successive fibonacci numbers and add them // to the sum if the value is even. while b <= 4_000_000 { if 0 == b % 2 { sum += b; } temp = a + b; a = b; b = temp; } println!("Sum of even Fibonacci terms below 4 million is {}", sum); } 
\$\endgroup\$
5
  • 1
    \$\begingroup\$Modifying your question in such a way as to invalidate any existing answers is considered "breaking site rules"...\$\endgroup\$
    – Fe2O3
    CommentedDec 27, 2024 at 0:33
  • \$\begingroup\$I'm not trying to be antagonistic, but I feel like the change was a clarification and did not alter the question's meaning or intent. The original question indicates I'm learning Rust and asked if my code was "rusty", not if my solutions were good. I thought it was apparent I was looking for feedback on the code itself and not the solutions to the problems which were just my medium for learning Rust.\$\endgroup\$
    – zephyr
    CommentedDec 27, 2024 at 0:49
  • 1
    \$\begingroup\$@Fe2O3 The OP can edit the English section, clarifying what the OP wants is fine. The way the any and all rule works is even if an OP asks for a specific piece of advice then answerers can just straight up ignore the request. The edit doesn't invalidate your answer because the code hasn't be changed and so the answer is still valid as all requests can be ignored. If the OP doesn't think your answer is what is desired then you know feels bad, but others will appreciate the feedback. You just won't get the accept, nor would you before the edit.\$\endgroup\$
    – Peilonrayz
    CommentedDec 27, 2024 at 1:08
  • \$\begingroup\$@Peilonrayz Thank you. The title, first paragraph, code and tag all indicate "Rust" is paramount. The dispatch function (main()) runs a timer, and the "fizzbuzz" summation is presented in two versions (one un-called). I chose to address what I viewed as correctable shortcomings in the inefficient posted code for problem #2... While I understand and accept the OP is looking for a "Rust Review", the precursor to coding, in any language, should be choosing the best algorithm one can... Good coding is much more than typing syntactic statements... :-) Cheers, and Happy New Year! :-)\$\endgroup\$
    – Fe2O3
    CommentedDec 27, 2024 at 5:20
  • 1
    \$\begingroup\$Please remember that Euler, Spoj etc are not sites that are intended to improve one's computer language skills. Those are a given – they are about algorithms and often, an understanding of the underlying principles. In the same way, crosswords are not a good language learning tool.\$\endgroup\$CommentedDec 27, 2024 at 20:20

2 Answers 2

5
\$\begingroup\$

Code that works and is comprehensible is good. It's good to write a plodding and primitive version in order to satisfy oneself that concepts are understood, and to have a trusted version of the results. "Gold Star" coders are those who are not content to consider the job complete until they are satisfied that the algorithm is the best it can be.

  • Regardless of what is presented in the problem statement, the unassailable authority of Wikipedia shows that the Fibonacci Sequence begins with 0:
    0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ....
    AND, the presented version neglects the two instances of 1 in the embedded comment.

  • Understanding addition of 'even' and 'odd' numbers reveals that the sum of two values reflects the similarity or difference of the two values (being even or odd).

  • Studying the first few numbers of the Fibonacci sequence that begins with 0, one discovers a pattern of "EooEooEooEooEooE..." (where 'E' is "even" and 'o' is "odd".)

With this in mind, it becomes apparent that the body of the loop could be made slightly larger, and the repeated testing for 'even' becomes unnecessary.

Code changes left as an exercise for the OP.


Moral: Code must deliver correct results, and be completed within the available timeframe. For "exercises" intended to sharpen one's skills, it's good to go back and look for what might have been overlooked in the first pass. This is how one improves one's ability to analyse a problem (from a coders perspective) before turning to the keyboard to begin typing.


Thinking even deeper about the sequence and the challenge, one finds even better ways to generate the interesting values that are summed... Discovery is its own reward.


Further commentary

Although the two problems are somewhat different, the presented Rust code that solves each is comprised of the same elements (i.e.: discrete variables, arithmetic operations and loops).

Contemplating the first challenge (with its "fizzbuzz" feel), there's a missed opportunity to use a slightly more advanced data structure: an array...

Consider the first seven values to be summed (beginning with 0 again):
0, 3, 5, 6, 9, 10, and 12
The next seven values are each 15 greater than the first set of seven values.

Some quick pencil work reveals that each element of every succeeding group of seven values is, likewise, n + 15 higher than its previous 'cousin'.

To really become acquainted a language, it would be good to stay with one challenge and attempt alternative solutions that use heretofore unexplored aspects of the language.

Just as the "Fibonacci" problem has been addressed with two variables initialised with starting values, I suggest addressing Problem #1 by Rust code that initialises and efficiently updates one array of seven elements, summing those elements until the ceiling "bail out" is reached. (This will inspire seeing regularities and patterns that suggest a formulaic solution, instead of an iterative procedure. Strive to become a "Gold Star" coder, using Rust or using whatever language you choose!)

\$\endgroup\$
4
  • \$\begingroup\$Hint: Once there are a few summable values generated, look carefully at those values. Can you find a relationship between 0, 2 and 8 that applies to 2, 8 and 34 (and 8, 34 and 144...)?\$\endgroup\$
    – Fe2O3
    CommentedDec 26, 2024 at 23:32
  • \$\begingroup\$I appreciate the input on this. The comment at the top of problem 2 is taken verbatim from the challenge as a reference. Your note about getting to a solution faster/better is nice, but I was mostly using the Euler problems as a framework for learning Rust. If you had any insights into improvements from a rust language perspective that could be made, I'd love to hear that.\$\endgroup\$
    – zephyr
    CommentedDec 27, 2024 at 0:05
  • \$\begingroup\$Given that I don't write Rust, another "general" comment (based on two examples) is the appearance of repeated similar output statements (header/footer) in both functions... Rust or not, (near) duplication of code should be avoided... Perhaps func_map needs to be slightly more complicated so that "helper functions" can be simplified... (Imagine 200+ problems, each with their own header/footer... Yech...) Cheers! :-)\$\endgroup\$
    – Fe2O3
    CommentedDec 27, 2024 at 1:33
  • \$\begingroup\$For many Euler problems, “is correct and readable” won’t give you a solution in reasonable time. (Assuming a new problem every week, one week to find an answer is just about “reasonable” if you are patient).\$\endgroup\$CommentedDec 27, 2024 at 13:05
5
\$\begingroup\$

Why not use a list of functions instead of a hash map if you want to preserve order?

// Define the list of function pointers to each problem's solution let funcs: Vec<(&str, fn())> = vec![ ("1", multiples_of_3_or_5), ("2", even_fib_numbers), // ... More functions added here ]; let args: HashSet<String> = args().skip(1).collect(); let start = Instant::now(); for (problem_id, problem_fn) in funcs { if args.is_empty() || args.contains(problem_id) { let problem_start = Instant::now(); problem_fn(); println!("Completed in {:?}\n", problem_start.elapsed()); } } 

Another option is to use numbers as a problem identifiers and store them into BtreeMap instead of HashMap. Iteration over BtreeMap is done in key order.

\$\endgroup\$
1
  • \$\begingroup\$+1, in fact, the list of functions could be a const array: const SOLUTIONS: &[(&str, fn())] = &[ ("1", f1), ("2", f2), ... ];\$\endgroup\$CommentedFeb 2 at 18:19

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.