A simple trait-based framework for the annual Advent of Code programming challenge.

Overview

lib_aoc

lib_aoc is a simple trait-based framework for the annual Advent of Code programming challenge.

Focus less on the boilerplate and more on the problem by automatically wiring up your solutions with input loading, pretty-printing and granular benchmarking.

Getting Started

Create a new binary crate and add lib_aoc as a dependency.

$ cargo new advent_of_code && cd advent_of_code
$ cargo add lib_aoc

Then, import the lib_aoc prelude and create a new struct to link your solutions.

use lib_aoc::prelude::*;

// Can be named whatever you'd like.
struct Solutions {}

fn main() { /* ... */ }

When solving a problem, you'll implement the Solution trait on this struct, and lib_aoc will take care of connecting everything together.

Before you can do that, however, you'll need to implement the Solver trait on the struct, which (among other, optional things) tells lib_aoc how you'd like puzzle inputs to be loaded.

The simple approach is to just read the input from disk, but more complex approaches (such as scraping the Advent of Code website directly) are certainly possible.

impl Solver for Solutions {
    fn load(day: u8) -> String {
        std::fs::read_to_string(format!("src/inputs/{day:02}.txt"))
            .expect("Puzzle input could not be read.")
    }
    
    // Note that a test loading implementation can be elided if one is not desired;
    // the default implementation will simply panic.
    fn load_test(day: u8) -> String {
        std::fs::read_to_string(format!("src/inputs/test_{day:02}.txt"))
            .expect("Puzzle input could not be read.")
    }
}

With Solver implemented, you can now begin solving problems!

Implementing a Solution

For demonstration purposes, we'll assume a simple first problem:

  • The input is a list of integers, one per line.
  • Part one wants the sum of all the integers.
  • Part two wants us to square each integer, then sum them.

Start by implementing Solution<DAY_01> for your solutions struct; at minimum, you need to provide type definitions for Input and Output, as well as an implementation of parse.

impl Solution<DAY_01> for Solutions {
    type Input<'i> = Vec<u64>;
    type Output = u64;

    fn parse(puzzle: &str) -> Self::Input<'_> {
        puzzle
            .lines()
            .map(str::parse::<u64>())
            .map(Result::unwrap)
            .collect::<Vec<_>>() 
    }
}

At this point, the solution is technically ready to be run. You can use the solve_through macro to execute all solutions up to a certain day, like so:

fn main() {
    // Notes: 
    // - Due to macro limitations, you must use an integer literal for the day cap.
    // - Trying to solve through days you haven't implemented yet is a compile error.
    solve_through!(Solutions, 1);
}

Assuming your load implementation works, the program should output something like this:

--- DAY 1 ---
Part 1: unimplemented
Part 2: unimplemented

--- BENCH (RELEASE) ---
Parsing: 20 ns
Part 1: 20 ns
Part 2: 20 ns
Total: 60 ns

It looks like the actual solution logic is unimplemented! Fortunately, that's easy to fix - we just implement the part_one and part_two methods.

impl Solution<DAY_01> for Solutions {
    type Input<'i> = Vec<u64>;
    type Output = u64;

    fn parse(puzzle: &str) -> Self::Input<'_> {
        puzzle
            .lines()
            .map(str::parse::<u64>())
            .map(Result::unwrap)
            .collect::<Vec<_>>() 
    }

    fn part_one(input: &Self::Input<'_>) -> Self::Output {
        input.iter()
            .sum::<u64>()
            .into()
    }

    fn part_two(input: &Self::Input<'_>) -> Self::Output {
        input.iter()
            .map(|x| x.pow(2) )
            .sum::<u64>()
            .into()
    }
}

As you can see, the signatures of the solver methods are identical apart from their names - they take a shared reference to a value of type Input and return an Output.

The default implementations of these methods panic, which (by using std::panic::catch_unwind) is how lib_aoc knew to display unimplemented when the program was run earlier. By overriding them with implementations that don't panic and instead return a proper value, the result will be displayed instead:

--- DAY 1 ---
Part 1: 2506
Part 2: 95843

--- BENCH (RELEASE) ---
Parsing: 7.223 µs
Part 1: 73.838 µs
Part 2: 81.042 µs
Total: 162.244 µs

And that's it - you've implemented a solution!

Deriving Tests

Because Advent of Code provides a test case in the description of every problem, lib_aoc also comes with a macro for deriving tests from your Solution implementations.

Assuming your load_test implementation already correctly loads test cases, all you need to do is implement the Test trait on your solution to provide the expected results:

impl Test<DAY_01> for Solutions {
    fn expected(part: bool) -> Self::Output {
        // If you don't know the expected result for a part yet, you can just
        // substitute a panicking macro.
        match part {
            // PART_ONE and PART_TWO are constants from the prelude.
            PART_ONE => 24_000,
            PART_TWO => 34_000
        }
    }
} 

Then you can invoke the derive_tests macro to auto-generate the tests:

derive_tests!(Solutions, DAY_01);

This expands into a new module with a test function for each part of the solution, and can be run normally via cargo test.

Notes on Benchmarking

lib_aoc provides basic benchmarking of solution implementations via std::time::Instant. While the measurements it provides are good approximations, a crate like criterion is a better choice if you want a more rigorous solution.

Also note that execution clock is started after your Solver::load implementation returns, immediately before Solution::parse is invoked. This means the time spent loading the puzzle input is not considered by the benchmark.

Additional Customization Options

By overriding the Solver::display and Solver::finalize methods, it's possible to define custom behavior that is invoked once a solution finishes executing in a non-test context.

display has a default implementation that pretty-prints the solution outcome, while finalize defaults to a no-op. Both methods take a shared reference to an Outcome<impl Display>.

Want to add some awesome extra behavior like submitting your solution to AoC right from the command line? You can do that here!

You might also like...
Advent of Code 2021 puzzles & solutions in Rust

Advent of Code 2021 These are puzzles for the Advent of Code 2021 challenge, written and solved in the Rust programming language. The puzzle for each

Repository with my Advent of Code 2021 puzzle solutions 🎄
Repository with my Advent of Code 2021 puzzle solutions 🎄

🎄 Advent of Code 2021 🎄 I decided to stick with Rust this year and try to improve a bit on it, I basically haven't used it since last year's AoC, so

Advent of Code 2021 (Rust)

aoc-2021 Advent of Code 2021 with Rust. To build and run the project This project uses cargo-aoc. More detailed instructions can be found at that proj

Advent of Code 2021, also an attempt to practice a bit of Rust.

Advent of Code 2021 Advent of Code 2021 (my first one!), also an attempt to practice a bit of Rust. Running (Assuming that the respective inputs are i

My solutions to Advent of Code 2021 (mostly in rust)

Advent of Code 2021 Small code to solve problems at https://adventofcode.com/2021. Most of the code are written in Rust. How to run solutions For exam

Learning Rust through Advent of Code 2021 - probably not very clean!

AoC 2021 ======== I'm using AoC2021 as an excuse to learn Rust (and maybe some other languages). Please do *not* use this repository as a good source

Quick'n dirty macro set for advent of code 2021

AOC quick'n dirty macro set Those are implemented using quick'n dirty procedural macros and libraries. Running the program Use cargo run --release --

🦀 Advent of Code 2021

Advent of Code 2021 License Licensed under either of Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) MIT li

Advent of Code - 2021

Advent of Code - 2021 Solutions to the Advent of Code 2021 written in Rust using cargo-aoc. Instructions Install cargo-aoc with cargo install cargo-ao

Rust-advent - Learning Rust by solving advent of code challenges (Streaming live on Twitch every Monday)

Rust advent ?? ?? Learning Rust by implementing solutions for Advent of Code problems. ?? HEY, we are live-streaming our attempts to solve the exercis

Luciano Mammino 20 Nov 11, 2022
🎄My Advent of Code 2021 solutions in the Rust programming language

Advent of Code 2021 in Rust My Advent of Code 2021 solutions in the Rust programming language. This repository holds a separate Rust project for each

Tim Visée 227 Dec 16, 2022
My Advent of Code 2023 solutions in the Rust programming language.

Advent of Code 2023 in Rust My Advent of Code 2023 solutions in the Rust programming language. This repository holds a separate Rust project for each

Tim Visée 65 Dec 17, 2023
My solutions for the 2021 edition of the Advent of Code, using Rust and SOM (Simple Object Machine)

Advent of Code 2021 These are my solutions for the 2021 edition of the Advent of Code. The solutions are all implemented using both Rust and SOM (Simp

Nicolas Polomack 1 Dec 23, 2021
Shellfirm - Intercept any risky patterns (default or defined by you) and prompt you a small challenge for double verification

shellfirm Opppppsss you did it again? ?? ?? ?? Protect yourself from yourself! rm -rf * git reset --hard before saving? kubectl delete ns which going

elad 652 Dec 29, 2022
Rust vs C: Software Graphics Challenge

Rust vs C: Reto de Gráficos por Software Aprende Rust y C programando gráficos por software con efectos de demoscene clásica de los años 80. Episodios

null 9 Aug 26, 2023
Extension trait to chunk iterators into const-length arrays.

const-chunks This crate provides an extension trait that lets you chunk iterators into constant-length arrays using const generics. See the docs for m

Louis Gariépy 6 Jun 12, 2023
This library provides a convenient derive macro for the standard library's std::error::Error trait.

derive(Error) This library provides a convenient derive macro for the standard library's std::error::Error trait. [dependencies] therror = "1.0" Compi

Sebastian Thiel 5 Oct 23, 2023
⭐ Advent of Code 2020: Мade with Rust

Advent of Code 2020: Мade with Rust When I was solving puzzles, my goal was to practice writing idiomatic Rust. My solutions do not claim to be the fa

Sergey Grishakov 9 Sep 15, 2022
⭐ Advent of Code 2021: Мade with Rust

Advent of Code 2021: Мade with Rust When I was solving puzzles, my goal was to practice writing idiomatic Rust. My solutions do not claim to be the fa

Sergey Grishakov 13 Dec 2, 2021