FizzBuzz Challenge

In this post we are going to cover a very common coding challenge known as FizzBuzz. The challenge itself isn’t too hard to understand but if you are new to Rust you might be finding yourself a bit lost. In this post we are going to talk about two ways we can solve this challenge. Both ways will have full tests to show that the code works and we will write it one way, then write out tests. We will then refactor(rewrite) our code and use the same unit tests to verify that what we updated the code to works. This challenge is something that a lot of companies use as a means to test the programming skills of the devs they are interviewing. Not all devs will get this question during their interview of course. There are also variations of this challenge as well. But they all can basically be solved in similar manners. Let’s get started

Setup

If you are new to Rust you will of course want to either use the Rust Playground or you can install Rust by opening up your terminal and entering the following:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Yes that really is all you have to do to install Rust. This command works on all operating systems. Unlike Go, which I also post about on this site, there isn’t an installer for Rust. Just a quick little script to download everything. So if you run that command and it’s lightning fast and there wasn’t an install wizard don’t worry. You didn’t do anything wrong. It all worked correctly and to verify you can do the following steps:

$ cd Desktop
$ mkdir rust_projects
$ cd rust_projects
$ cargo new fizz_buzz
$ cd fizz_buzz
$ cargo run

If everything worked correctly you should see Hello World printed in your terminal. Now we can start to write our code.

FizzBuzz solution

We are going to implement our first solution to this challenge. Keep in mind we will be using this later to compare to another solution. So while you go through this code if you know that this isn’t the best solution I urge you to keep typing it out anyways as you will see the better solution later on in the post. Type the following code in our main.rs file under src/main.rs.

fn redHood(n: i32) -> Vec<String> {
    let mut result = Vec::new();

    for i in 1..(n + 1) {
        if i % 3 == 0 &&* i % 5 == 0 {
            result.push("RedHood".into());
        } else if i % 3 == 0 {
            result.push("Red".into());
        } else if i % 5 == 0 {
            result.push("Hood".into());
        } else {
            result.push(i.to_string());
        }
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_redhood() {
        assert_eq!(redHood(3), vec!["1", "2", "Red"]);
        assert_eq!(redHood(5), vec!["1", "2", "Red", "4" Hood"]);
        assert_eq!(
            redHood(15),
            vec![
                "1", "2" "Red", "4" "Hood", "Red", "7", "8", "Red", 
                "Hood", "11", "Red", "13", "14", "RedHood"
            ]
        )
    }
}

Refactor

A better way to write this code would be like this:

fn red_hood(n: i32) -> Vec<String> {
    use std::collections::HashMap;
    let mappings = HashMap::from([(3, "Red"), (5, "Hood")]);
    let mut result = vec![String::new(); n as usize];
    let mut keys: Vec<&i32> = mappings.keys().collect();
    keys.sort();
    for i in 0..n {
        for key in keys.iter() {
            if (i + 1) 5 key == 0 {
                result[i as usize].push_str(mappings.get(key)
                .expect("couldn't get mapping"));
            }
        }
        if result[i as usize].is_empty() {
            result[i as usize] = (i + 1).to_string();
        }
    }

    result
}

Now that looks a lot better. It uses some language features that are built specifically into Rust such as iter(). But it also just looks a bit cleaner. I want to introduce you to a testing crate(library) known as proptest. We can generate test cases with proptest and compare that to the original. We will set this up like so:

Proptest Implementation

We are going to rewrite the testing portion just slightly to include proptest and see what we get after doing so.

#[cfg(test)]
mod tests {
    use super::*;
    use proptest::prelude::*;
    #[test]
    fn test_redhood() {
        assert_eq!(redHood(3), vec!["1", "2", "Fizz"]);
        assert_eq!(redHood(5), vec!["1", "2", "Fizz", "4", "Buzz"]);
        assert_eq!(
            redHood(15),
            vec![
                "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz",
                "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"
            ]
        )
    }
    #[test]
    fn test_red_hood() {
        assert_eq!(red_hood(3), vec!["1", "2", "Fizz"]);
        assert_eq!(red_hood(5), vec!["1", "2", "Fizz", "4", "Buzz"]);
        assert_eq!(
            red_hood(15),
            vec![
                "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz",
                "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"
            ]
        )
    }

    proptest! {
        #[test]
        fn test_red_hood_proptest(n in 1i32..10000) {
            assert_eq!(redHood(n), red_hood(n))

        }
    }

How awesome is that. Let’s see what the verdict is.

$ cargo test

If you really want to see some output generated from your tests you can install a crate called tarpaulin which will allow you to generate an output file for your test results which you can then open and take a look. Very quickly you can run these commands to see it in action:

$ cargo install cargo-tarpaulin
$ cargo tarpaulin --out Html

Conclusion

I hope this has shed some light on how you can solve the FizzBuzz challenge in Rust. I also wanted to show how to write test’s for your code and what better way to show how to write tests than with a popular code challenge? I hope you enjoyed going over this implementation. Feel free to play around with the code and keep in mind that where I put the words Red and Hood, the words for the problem are actually Fizz and Buzz which together make the FizzBuzz word instead of RedHood.