feat: prepare, solve, solve all
This commit is contained in:
parent
0000000030
commit
0000001099
|
@ -0,0 +1,3 @@
|
||||||
|
[alias]
|
||||||
|
prepare = "run --bin prepare --"
|
||||||
|
solve = "run --bin"
|
|
@ -0,0 +1,2 @@
|
||||||
|
TOKEN=secret
|
||||||
|
YEAR=2022
|
|
@ -1,10 +1,18 @@
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
debug/
|
||||||
|
target/
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# aoc-template
|
||||||
|
# env vars
|
||||||
|
.env
|
||||||
|
|
||||||
|
# downloaded inputs
|
||||||
|
src/inputs/
|
||||||
|
!src/inputs/keep
|
||||||
|
|
||||||
|
src/test_inputs/
|
||||||
|
!src/test_inputs/keep
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pico-args",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "aoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Matej Janežič <janezic.mj@gmail.com>"]
|
||||||
|
default-run = "aoc"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pico-args = "0.5.0"
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Advent-of-Code {YEAR}
|
||||||
|
*This is a dumbed down version of [fspoettel/advent-of-code-rust](https://github.com/fspoettel/advent-of-code-rust/blob/main/Cargo.toml) with some extra features*
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
### Prepare
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# example: `cargo prepare 1`
|
||||||
|
cargo prepare <day>
|
||||||
|
|
||||||
|
# output:
|
||||||
|
# Created module "src/bin/01.rs"
|
||||||
|
# Created empty input file "src/inputs/01.txt"
|
||||||
|
# Created empty example file "src/examples/01.txt"
|
||||||
|
# ---
|
||||||
|
# 🎄 Type `cargo solve 01` to run your solution.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solve
|
||||||
|
```sh
|
||||||
|
# example: `cargo solve 01`
|
||||||
|
cargo solve <day>
|
||||||
|
|
||||||
|
# output:
|
||||||
|
# Running `target/debug/01`
|
||||||
|
# 🎄 Part 1 🎄
|
||||||
|
#
|
||||||
|
# 6 (elapsed: 37.03µs)
|
||||||
|
#
|
||||||
|
# 🎄 Part 2 🎄
|
||||||
|
#
|
||||||
|
# 9 (elapsed: 33.18µs)
|
||||||
|
```
|
||||||
|
Displayed timings show the raw execution time of your solution without overhead (e.g. file reads). To run an optimized version for benchmarking, append the `--release` flag.
|
||||||
|
|
||||||
|
|
||||||
|
### Solve all
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo all
|
||||||
|
|
||||||
|
# output:
|
||||||
|
# Running `target/release/aoc`
|
||||||
|
# ----------
|
||||||
|
# | Day 01 |
|
||||||
|
# ----------
|
||||||
|
# 🎄 Part 1 🎄
|
||||||
|
#
|
||||||
|
# 0 (elapsed: 170.00µs)
|
||||||
|
#
|
||||||
|
# 🎄 Part 2 🎄
|
||||||
|
#
|
||||||
|
# 0 (elapsed: 30.00µs)
|
||||||
|
# <...other days...>
|
||||||
|
# Total: 0.20ms
|
||||||
|
```
|
||||||
|
|
||||||
|
`all` is an alias for `cargo run`. To run an optimized version for benchmarking, append the `--release` flag.
|
||||||
|
|
||||||
|
### Run against test inputs
|
||||||
|
run all solutions
|
||||||
|
```sh
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
run for a given day
|
||||||
|
```sh
|
||||||
|
cargo test --bin <day>
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* This file contains template code.
|
||||||
|
* There is no need to edit this file unless you want to change template functionality.
|
||||||
|
*/
|
||||||
|
use std::{
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io::Write,
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn part_two(input: &str) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let input = &aoc::read_file("inputs", DAY);
|
||||||
|
aoc::solve!(1, part_one, input);
|
||||||
|
aoc::solve!(2, part_two, input);
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
let input = aoc::read_file("test_inputs", DAY);
|
||||||
|
assert_eq!(part_one(&input), None);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
let input = aoc::read_file("test_inputs", DAY);
|
||||||
|
assert_eq!(part_two(&input), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"###;
|
||||||
|
|
||||||
|
fn parse_args() -> Result<u8, pico_args::Error> {
|
||||||
|
let mut args = pico_args::Arguments::from_env();
|
||||||
|
args.free_from_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_create_file(path: &str) -> Result<File, std::io::Error> {
|
||||||
|
OpenOptions::new().write(true).create_new(true).open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_file(path: &str) -> Result<File, std::io::Error> {
|
||||||
|
OpenOptions::new().write(true).create(true).open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let day = match parse_args() {
|
||||||
|
Ok(day) => day,
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Need to specify a day (as integer). example: `cargo prepare 7`");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let day_padded = format!("{:02}", day);
|
||||||
|
|
||||||
|
let input_path = format!("src/inputs/{}.txt", day_padded);
|
||||||
|
let example_path = format!("src/test_inputs/{}.txt", day_padded);
|
||||||
|
let module_path = format!("src/bin/{}.rs", day_padded);
|
||||||
|
|
||||||
|
let mut file = match safe_create_file(&module_path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to create module file: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match file.write_all(MODULE_TEMPLATE.replace("DAY", &day.to_string()).as_bytes()) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Created module file \"{}\"", &module_path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to write module contents: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match create_file(&input_path) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Created empty input file \"{}\"", &input_path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to create input file: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match create_file(&example_path) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Created empty example file \"{}\"", &example_path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to create example file: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("---");
|
||||||
|
println!(
|
||||||
|
"🎄 Type `cargo solve {}` to run your solution.",
|
||||||
|
&day_padded
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* This file contains template code.
|
||||||
|
* There is no need to edit this file unless you want to change template functionality.
|
||||||
|
* Prefer `./helpers.rs` if you want to extract code from your solutions.
|
||||||
|
*/
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub mod helpers;
|
||||||
|
|
||||||
|
pub const ANSI_ITALIC: &str = "\x1b[3m";
|
||||||
|
pub const ANSI_BOLD: &str = "\x1b[1m";
|
||||||
|
pub const ANSI_RESET: &str = "\x1b[0m";
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! solve {
|
||||||
|
($part:expr, $solver:ident, $input:expr) => {{
|
||||||
|
use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) {
|
||||||
|
let timer = Instant::now();
|
||||||
|
let result = func(input);
|
||||||
|
let elapsed = timer.elapsed();
|
||||||
|
match result {
|
||||||
|
Some(result) => {
|
||||||
|
println!(
|
||||||
|
"{} {}(elapsed: {:.2?}){}",
|
||||||
|
result, ANSI_ITALIC, elapsed, ANSI_RESET
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("not solved.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("🎄 {}Part {}{} 🎄", ANSI_BOLD, $part, ANSI_RESET);
|
||||||
|
print_result($solver, $input);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_file(folder: &str, day: u8) -> String {
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
|
||||||
|
let filepath = cwd.join("src").join(folder).join(format!("{:02}.txt", day));
|
||||||
|
|
||||||
|
let f = fs::read_to_string(filepath);
|
||||||
|
f.expect("could not open input file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_time(val: &str, postfix: &str) -> f64 {
|
||||||
|
val.split(postfix).next().unwrap().parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_exec_time(output: &str) -> f64 {
|
||||||
|
output.lines().fold(0_f64, |acc, l| {
|
||||||
|
if !l.contains("elapsed:") {
|
||||||
|
acc
|
||||||
|
} else {
|
||||||
|
let timing = l.split("(elapsed: ").last().unwrap();
|
||||||
|
// use `contains` istd. of `ends_with`: string may contain ANSI escape sequences.
|
||||||
|
// for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200
|
||||||
|
if timing.contains("ns)") {
|
||||||
|
acc // range below rounding precision.
|
||||||
|
} else if timing.contains("µs)") {
|
||||||
|
acc + parse_time(timing, "µs") / 1000_f64
|
||||||
|
} else if timing.contains("ms)") {
|
||||||
|
acc + parse_time(timing, "ms")
|
||||||
|
} else if timing.contains("s)") {
|
||||||
|
acc + parse_time(timing, "s") * 1000_f64
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! assert_approx_eq {
|
||||||
|
($a:expr, $b:expr) => {{
|
||||||
|
let (a, b) = (&$a, &$b);
|
||||||
|
assert!(
|
||||||
|
(*a - *b).abs() < 1.0e-6,
|
||||||
|
"{} is not approximately equal to {}",
|
||||||
|
*a,
|
||||||
|
*b
|
||||||
|
);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_exec_time() {
|
||||||
|
assert_approx_eq!(
|
||||||
|
parse_exec_time(&format!(
|
||||||
|
"🎄 Part 1 🎄\n0 (elapsed: 74.13ns){}\n🎄 Part 2 🎄\n0 (elapsed: 50.00ns){}",
|
||||||
|
ANSI_RESET, ANSI_RESET
|
||||||
|
)),
|
||||||
|
0_f64
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_approx_eq!(
|
||||||
|
parse_exec_time("🎄 Part 1 🎄\n0 (elapsed: 755µs)\n🎄 Part 2 🎄\n0 (elapsed: 700µs)"),
|
||||||
|
1.455_f64
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_approx_eq!(
|
||||||
|
parse_exec_time("🎄 Part 1 🎄\n0 (elapsed: 70µs)\n🎄 Part 2 🎄\n0 (elapsed: 1.45ms)"),
|
||||||
|
1.52_f64
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_approx_eq!(
|
||||||
|
parse_exec_time(
|
||||||
|
"🎄 Part 1 🎄\n0 (elapsed: 10.3s)\n🎄 Part 2 🎄\n0 (elapsed: 100.50ms)"
|
||||||
|
),
|
||||||
|
10400.50_f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* This file contains template code.
|
||||||
|
* There is no need to edit this file unless you want to change template functionality.
|
||||||
|
*/
|
||||||
|
use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let total: f64 = (1..=25)
|
||||||
|
.map(|day| {
|
||||||
|
let day = format!("{:02}", day);
|
||||||
|
|
||||||
|
let cmd = Command::new("cargo")
|
||||||
|
.args(["run", "--release", "--bin", &day])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("----------");
|
||||||
|
println!("{}| Day {} |{}", ANSI_BOLD, day, ANSI_RESET);
|
||||||
|
println!("----------");
|
||||||
|
|
||||||
|
let output = String::from_utf8(cmd.stdout).unwrap();
|
||||||
|
let is_empty = output.is_empty();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
if is_empty {
|
||||||
|
"Not solved."
|
||||||
|
} else {
|
||||||
|
output.trim()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if is_empty {
|
||||||
|
0_f64
|
||||||
|
} else {
|
||||||
|
aoc::parse_exec_time(&output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}Total:{} {}{:.2}ms{}",
|
||||||
|
ANSI_BOLD, ANSI_RESET, ANSI_ITALIC, total, ANSI_RESET
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue