Reaves.dev

v0.1.0

built using

Phoenix v1.7.12

Advent Of Code 2022 - Setup

Stephen M. Reaves

::

2022-12-02

Advent of Code 2022 - Setup

Advent of Code is back! This means we can walk through the coding problems and try to come up with some solutions. I will be going through this in Rust as I am trying to learn the language. I will be giving a computer science talk at Ichiban 2023 and I will be going back through these solutions but in Golang.

Setup

Before we get too far down the road, I want to set my project up in such a way that executing code from various days will be pretty similar. And since I’m going to be rereading later, I want to make sure the code is understandable later. This is the solution I came up with.

.
├── Cargo.lock
├── Cargo.toml
├── input.txt
└── src
    ├── args
    │   └── mod.rs
    ├── main.rs
    ├── part_one
    │   └── mod.rs
    └── part_two
        └── mod.rs

Cargo.lock and Cargo.toml are standard rust features. input.txt is (obviously) the input file that we will get from advent of code. The real meat and potatoes comes from the src directory. Let’s start with main.rs.

Main

We start with including the other modules. In rust that looks like this:

mod args;
mod part_one;
mod part_two;

Those are the three other files in our src directory. We also need to include any third party libraries that we’re using. In our case its just one.

use clap::Parser;

This let’s us parse some input flags in a cool way. All that’s left is our main function.

fn main() {
    match args::parse() {
        (1, a) => part_one::run(a),
        (2, a) => part_two::run(a),
        _ => eprintln!(Not implmented),
    }
}

As you can see, all we really do parse the cli flags and pack them into the match clause. The parse method returns a tuple containing the part to run as well as an args::Arguments struct. That struct is defined in the args module and controls basic things like which input file we are using.

main.rs
mod args;
mod part_one;
mod part_two;

use clap::Parser;

fn main() {
    match args::parse() {
        (1, a) => part_one::run(a),
        (2, a) => part_two::run(a),
        _ => eprintln!(Not implmented),
    }
}

Args

The args module handles parsing command line flags and options. We are using the clap package to do a lot of the heavy lifting. Clap does a lot of magic using macros that lets us get away with not writing too much boilerplate. Basically, we have one struct that holds all the options for the program, document accordingly, and everything just works. Let’s take a look.

Once again, we have some things to include.

use clap::Parser;
use std::path::PathBuf;

The first one, again, is to handle cli flags, and the second one simply lets us read from files.

pub static DEFAULT_INPUT: &str = input.txt;

This line defines a default input file. Later we give the option to specify an alternative one, but I don’t want to have to.

#[derive(Debug, Parser)]
#[clap(
    author = "Stephen M. Reaves",
    version = "2022.01",
    about = "Solves Advent of Code 2022 Day 1 - https://adventofcode.com/2022/day/1"
)]
pub struct Arguments {
    /// Which part are we doing? <1|2>
    #[clap(short, long, value_parser = clap::value_parser!(u8).range(1..3))]
    pub part: u8,

    /// Input file to use
    #[clap(short, long)]
    pub input: Option<PathBuf>,
}

Rust’s derive magic is amazing. This is what allows us to tell the compiler (and other libraries) to write code for us. In this derive statement, we get the compiler to handle debug information (useful when trying to print out a struct) and we get the clap package to create cli args based on the fields of this struct.

In the clap statement we provide a little extra information. This sort of information shows up when we give the -h flag.

The fields here use [documentation comments](https://doc.rust-lang.org/reference/comments.html will show up when you run cargo doc or, via clap when you run the binary with the -h flag. The clap directive does some more magic like short, which allows you to use -p for part and -i for input, long which allows you to do things like --part, and even a custom value parser. We’re also using a macro here to essentially only allow numbers in the range between 1 (inclusive) and 3 (exclusive). Basically that means you can only run this for parts 1 and 2, since that’s all the AoC has. The input field is wrapped in an option. This is a rust native idea, but clap extends this to mean that you don’t need to actually provide this on the cli if you don’t want to.

impl Arguments {
    pub fn get_input(self) -> PathBuf {
        self.input.unwrap_or(PathBuf::from(DEFAULT_INPUT))
    }
}

Lastly, we want to add some functionality to our struct. Basically, the get_input function simply returns the Arguments.input field if it’s not null, or the default file if it is null.

pub fn parse() -> (u8, Arguments) {
    let a = Arguments::parse();
    (a.part, a)
}

The parse method here is a wrapper for clap::parse and partially deconstructs the Arguments struct. We return a tuple containing the part and the struct to allow for a cleaner match statement in the main function.

args/mod.rs
use clap::Parser;
use std::path::PathBuf;

pub static DEFAULT_INPUT: &str = input.txt;

#[derive(Debug, Parser)]
#[clap(
    author = "Stephen M. Reaves",
    version = "2022.01",
    about = "Solves Advent of Code 2022 Day 1 - https://adventofcode.com/2022/day/1"
)]
pub struct Arguments {
    /// Which part are we doing? <1|2>
    #[clap(short, long, value_parser = clap::value_parser!(u8).range(1..3))]
    pub part: u8,

    /// Input file to use
    #[clap(short, long)]
    pub input: Option<PathBuf>,
}

impl Arguments {
    pub fn get_input(self) -> PathBuf {
        self.input.unwrap_or(PathBuf::from(DEFAULT_INPUT))
    }
}

pub fn parse() -> (u8, Arguments) {
    let a = Arguments::parse();
    (a.part, a)
}

Now we’re finally ready get started with the challenge.