Advent of Code Day 1 Solution

A picture of an advent calendar.

Category

Reading Time

6 minutes

My solution to the Advent of Code Day 1 Problem.

The Problem

The first problem is a simple aggregation and comparison to find the highest total.

The first problem details can be found here, but in summary we need to add up a series of totals delimited by returns and determine which elf is carrying the most reindeer food. After hearing much praise online about the new programming language Rust, I decided to give it a try and use it to solve this problem.

To start with, we need to read from a file and break the input into lines. Rust provides an easy way to do this:


    let result = fs::read_to_string("input.txt")
      .map_err(|_| "Couldn't parse file".to_string())
      .and_then(parse_input);
  

Rust adopts a very type-safe approach to IO, much like F#. It provides us a Result type that indicates what type of value will be returned if the operation is successful and what type will be returned if it fails. This way we know at all times that we’ve handled every possible case that could happen and we don’t have rely on reading the code and remembering all the possible branches.

Now that we’ve obtained the data from the file, we can transform it with additional functions. As you can see in the code, Rust provides a handy .and_then function that allows us to chain additional transforms. This is a pattern known as a monad, and it’s extremely helpful to provide focus to our code by allowing us to write the happy path. If at any point we get an Error, the chain will short-circuit and result will receive a value of Error which we can pattern match against later. Neat!

Writing the Aggregation

Now that we have the input, we need to parse it and put it all together.

To get the totals we need, we’ll need to reduce the array of strings, parse the numbers and sum them all together. This can be accomplished functionally with a simple fold and pattern match:

Rust has excellent support for pattern matching and we can take full advantage of it here. If we see a return, we know that we’ve reached the total of an elf and can compare it to the last largest total. If we get a number, we can parse and add it to the current running total. At the very end of this, we’ll have our max total.


  fn aggregate(acc: Result<(i32, i32), String>, calorie_count: &str) -> Result<(i32, i32), String> {
    match (acc, calorie_count) {
      (Ok((max, current)), "") if current > max => Ok((current, 0)),
      (Ok((max, current)), "") if max > current => Ok((max, 0)),
      (Ok((max, current)), _)                   => calorie_count.parse::<i32>()
                                                    .map(|i| (max, i + current)) 
                                                    .map_err(|_| "Unable to parse.".to_string()),
      (err, _)                                  => err
    }
  }
  

Putting It All Together

It's time to put everything together.

As can be seen in the aggregate function above, we get a Result back so we can simply use a monadic combinator to easily transform the input into our solution (.and_then). This results in the following code.

If you want to pull down and run my code, it can be found on my github.


    use std::fs;

    fn aggregate(acc: Result<(i32, i32), String>, calorie_count: &str) -> Result<(i32, i32), String> {
      match (acc, calorie_count) {
        (Ok((max, current)), "") if current > max => Ok((current, 0)),
        (Ok((max, current)), "") if max > current => Ok((max, 0)),
        (Ok((max, current)), _)                   => calorie_count.parse::<i32>()
                                                      .map(|i| (max, i + current)) 
                                                      .map_err(|_| "Unable to parse.".to_string()),
        (err, _)                                  => err
      }
    }

    fn parse_input(all_calorie_counts: String) -> Result<i32, String> {
      all_calorie_counts
        .lines()
        .fold(Ok((0,0)), aggregate)
        .map(|results| results.0)
    }

    fn main() {
      let result = fs::read_to_string("input.txt")
        .map_err(|_| "Ran into an error trying to read from input file: input.txt".to_string())
        .and_then(parse_input);

      match result {
        Ok(elf_with_max) => println!("Elf with the highest count: {elf_with_max}"),
        Err(err)         => println!("{err}")
      }
    }