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.