7 个不稳定版本
0.8.0 | 2023年12月1日 |
---|---|
0.7.2 | 2022年12月13日 |
0.6.2 | 2022年12月8日 |
0.5.0 | 2022年12月6日 |
#63 in 值格式化
44 次每月下载
30KB
315 行
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.
入门指南
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, part: bool) -> 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);
}
假设你的 load
实现工作正常,程序应该输出类似以下内容
--- DAY 1 ---
Part 1: unimplemented
Part 2: unimplemented
--- BENCH (RELEASE) ---
Parsing: 20 ns
Part 1: 20 ns
Part 2: 20 ns
Total: 60 ns
看起来实际的解决方案逻辑还没有实现!幸运的是,这很容易修复——我们只需要实现 part_one
和 part_two
方法。
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()
}
}
如你所见,求解器的签名除了名称外都是相同的——它们接受一个类型为 Input
的值的共享引用,并返回一个 Output
。
这些方法的默认实现会引发恐慌,这是通过使用 std::panic::catch_unwind
实现的,这就是为什么在程序运行时,lib_aoc
会显示 unimplemented
。通过用不引发恐慌而是返回正确值的实现覆盖它们,结果将被显示出来
--- 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
就这样——你已经实现了一个解决方案!
测试派生
由于 Advent of Code 在每个问题的描述中提供了一个测试用例,因此 lib_aoc
也提供了一个宏,可以从你的 Solution
实现中派生测试。
假设你的 load_test
实现已经正确加载了测试用例,你所需要做的就是实现你的解决方案上的 Test
特性,以提供预期的结果
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
}
}
}
然后你可以调用 derive_tests
宏来自动生成测试
derive_tests!(Solutions, DAY_01);
这会扩展到一个新模块,其中包含针对解决方案每个部分的测试函数,可以通过 cargo test
正常运行。
关于基准测试的说明
lib_aoc
通过 std::time::Instant
提供了解决方案实现的基准测试。虽然它提供的测量值是良好的近似值,但如果你需要一个更严格的解决方案,像 criterion
这样的 crate 是更好的选择。
此外,请注意,执行时钟是在你的 Solver::load
实现返回后、在调用 Solution::parse
之前启动的。这意味着加载谜题输入花费的时间不被基准测试考虑。
额外的自定义选项
通过覆盖 Solver::display
和 Solver::finalize
方法,可以在非测试上下文中在解决方案执行完成后定义自定义行为。
display
有一个默认实现,它会格式化输出解决方案的结果,而 finalize
默认不执行任何操作。这两个方法都接受一个 Outcome<impl Display>
的共享引用。
想要添加一些酷炫的额外行为,例如直接从命令行提交你的解决方案到 AoC?你可以在这里做到!
依赖关系
~0–9.5MB
~52K SLoC