2022-10-29 17:18:58 +02:00
|
|
|
/*
|
2022-10-29 17:25:11 +02:00
|
|
|
* This file contains template code.
|
|
|
|
* There is no need to edit this file unless you want to change template functionality.
|
2022-10-29 17:18:58 +02:00
|
|
|
* Prefer `./helpers.rs` if you want to extract code from your solutions.
|
|
|
|
*/
|
2021-12-29 14:12:01 +01:00
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
|
|
|
|
2022-10-17 11:19:08 +02:00
|
|
|
pub mod helpers;
|
|
|
|
|
2021-12-29 19:14:40 +01:00
|
|
|
pub const ANSI_ITALIC: &str = "\x1b[3m";
|
|
|
|
pub const ANSI_BOLD: &str = "\x1b[1m";
|
|
|
|
pub const ANSI_RESET: &str = "\x1b[0m";
|
2021-12-29 14:36:48 +01:00
|
|
|
|
|
|
|
#[macro_export]
|
2021-12-29 19:14:40 +01:00
|
|
|
macro_rules! solve {
|
2023-10-21 14:46:19 +02:00
|
|
|
($day:expr, $part:expr, $solver:ident, $input:expr) => {{
|
|
|
|
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, aoc_cli};
|
2021-12-29 14:36:48 +01:00
|
|
|
use std::fmt::Display;
|
|
|
|
use std::time::Instant;
|
2023-10-21 14:46:19 +02:00
|
|
|
use std::env;
|
|
|
|
use std::process;
|
|
|
|
|
|
|
|
fn submit_if_requested<T: Display>(result: T) {
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
|
|
|
|
if args.contains(&"--submit".into()) {
|
|
|
|
if aoc_cli::check().is_err() {
|
|
|
|
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
|
|
|
|
process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.len() < 3 {
|
|
|
|
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
|
|
|
|
process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1;
|
|
|
|
let part_submit = match args[part_index].parse::<u8>() {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(_) => {
|
|
|
|
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
|
|
|
|
process::exit(1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if part_submit == $part {
|
|
|
|
println!("Submitting puzzle answer for part {}...", $part);
|
|
|
|
aoc_cli::submit($day, $part, result).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 14:36:48 +01:00
|
|
|
|
2022-10-29 17:04:13 +02:00
|
|
|
fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) {
|
2021-12-29 14:36:48 +01:00
|
|
|
let timer = Instant::now();
|
|
|
|
let result = func(input);
|
2021-12-30 11:34:27 +01:00
|
|
|
let elapsed = timer.elapsed();
|
2022-10-29 17:04:13 +02:00
|
|
|
match result {
|
|
|
|
Some(result) => {
|
|
|
|
println!(
|
|
|
|
"{} {}(elapsed: {:.2?}){}",
|
|
|
|
result, ANSI_ITALIC, elapsed, ANSI_RESET
|
|
|
|
);
|
2023-10-21 14:46:19 +02:00
|
|
|
submit_if_requested(result);
|
2022-10-29 17:18:58 +02:00
|
|
|
}
|
2022-10-29 17:04:13 +02:00
|
|
|
None => {
|
|
|
|
println!("not solved.")
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 14:36:48 +01:00
|
|
|
}
|
|
|
|
|
2022-10-29 17:04:13 +02:00
|
|
|
println!("🎄 {}Part {}{} 🎄", ANSI_BOLD, $part, ANSI_RESET);
|
|
|
|
print_result($solver, $input);
|
2021-12-29 14:36:48 +01:00
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2021-12-29 14:12:01 +01:00
|
|
|
pub fn read_file(folder: &str, day: u8) -> String {
|
|
|
|
let cwd = env::current_dir().unwrap();
|
|
|
|
|
2023-02-04 22:17:04 +00:00
|
|
|
let filepath = cwd.join("src").join(folder).join(format!("{day:02}.txt"));
|
2021-12-29 14:12:01 +01:00
|
|
|
|
|
|
|
let f = fs::read_to_string(filepath);
|
|
|
|
f.expect("could not open input file")
|
|
|
|
}
|
2021-12-29 19:14:40 +01:00
|
|
|
|
|
|
|
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 {
|
2021-12-31 13:05:23 +01:00
|
|
|
let timing = l.split("(elapsed: ").last().unwrap();
|
|
|
|
// use `contains` istd. of `ends_with`: string may contain ANSI escape sequences.
|
2022-10-17 11:19:08 +02:00
|
|
|
// for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200
|
2021-12-31 13:05:23 +01:00
|
|
|
if timing.contains("ns)") {
|
2021-12-29 19:14:40 +01:00
|
|
|
acc // range below rounding precision.
|
2021-12-31 13:05:23 +01:00
|
|
|
} else if timing.contains("µs)") {
|
2021-12-29 19:14:40 +01:00
|
|
|
acc + parse_time(timing, "µs") / 1000_f64
|
2021-12-31 13:05:23 +01:00
|
|
|
} else if timing.contains("ms)") {
|
2021-12-29 19:14:40 +01:00
|
|
|
acc + parse_time(timing, "ms")
|
2021-12-31 13:05:23 +01:00
|
|
|
} else if timing.contains("s)") {
|
2021-12-29 19:14:40 +01:00
|
|
|
acc + parse_time(timing, "s") * 1000_f64
|
|
|
|
} else {
|
|
|
|
acc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-17 11:19:08 +02:00
|
|
|
/// 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
|
|
|
|
);
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2021-12-29 19:14:40 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_exec_time() {
|
|
|
|
assert_approx_eq!(
|
2021-12-31 13:05:23 +01:00
|
|
|
parse_exec_time(&format!(
|
|
|
|
"🎄 Part 1 🎄\n0 (elapsed: 74.13ns){}\n🎄 Part 2 🎄\n0 (elapsed: 50.00ns){}",
|
|
|
|
ANSI_RESET, ANSI_RESET
|
|
|
|
)),
|
2021-12-29 19:14:40 +01:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-12-06 18:22:40 +01:00
|
|
|
|
|
|
|
pub mod aoc_cli {
|
|
|
|
use std::{
|
|
|
|
fmt::Display,
|
|
|
|
process::{Command, Output, Stdio},
|
|
|
|
};
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
#[derive(Debug)]
|
2022-12-06 18:22:40 +01:00
|
|
|
pub enum AocCliError {
|
|
|
|
CommandNotFound,
|
|
|
|
CommandNotCallable,
|
|
|
|
BadExitStatus(Output),
|
|
|
|
IoError,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for AocCliError {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
match self {
|
|
|
|
AocCliError::CommandNotFound => write!(f, "aoc-cli is not present in environment."),
|
|
|
|
AocCliError::CommandNotCallable => write!(f, "aoc-cli could not be called."),
|
2022-12-07 00:09:33 +01:00
|
|
|
AocCliError::BadExitStatus(_) => {
|
|
|
|
write!(f, "aoc-cli exited with a non-zero status.")
|
|
|
|
}
|
2022-12-06 18:22:40 +01:00
|
|
|
AocCliError::IoError => write!(f, "could not write output files to file system."),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check() -> Result<(), AocCliError> {
|
|
|
|
Command::new("aoc")
|
|
|
|
.arg("-V")
|
|
|
|
.output()
|
|
|
|
.map_err(|_| AocCliError::CommandNotFound)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
pub fn read(day: u8) -> Result<Output, AocCliError> {
|
2022-12-06 18:22:40 +01:00
|
|
|
// TODO: output local puzzle if present.
|
2023-10-21 14:46:19 +02:00
|
|
|
let puzzle_path = get_puzzle_path(day);
|
|
|
|
|
|
|
|
let args = build_args(
|
|
|
|
"read",
|
|
|
|
&[
|
|
|
|
"--description-only".into(),
|
|
|
|
"--puzzle-file".into(),
|
|
|
|
puzzle_path,
|
|
|
|
],
|
|
|
|
day,
|
|
|
|
);
|
|
|
|
|
2022-12-06 18:22:40 +01:00
|
|
|
call_aoc_cli(&args)
|
|
|
|
}
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
pub fn download(day: u8) -> Result<Output, AocCliError> {
|
2022-12-06 18:22:40 +01:00
|
|
|
let input_path = get_input_path(day);
|
|
|
|
let puzzle_path = get_puzzle_path(day);
|
|
|
|
|
|
|
|
let args = build_args(
|
|
|
|
"download",
|
|
|
|
&[
|
|
|
|
"--overwrite".into(),
|
|
|
|
"--input-file".into(),
|
|
|
|
input_path.to_string(),
|
|
|
|
"--puzzle-file".into(),
|
|
|
|
puzzle_path.to_string(),
|
|
|
|
],
|
|
|
|
day,
|
|
|
|
);
|
|
|
|
|
|
|
|
let output = call_aoc_cli(&args)?;
|
|
|
|
|
|
|
|
if output.status.success() {
|
|
|
|
println!("---");
|
|
|
|
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
|
|
|
|
println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path);
|
|
|
|
Ok(output)
|
|
|
|
} else {
|
|
|
|
Err(AocCliError::BadExitStatus(output))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
pub fn submit<T: Display>(day: u8, part: u8, result: T) -> Result<Output, AocCliError> {
|
|
|
|
// workaround: the argument order is inverted for submit.
|
|
|
|
let mut args = build_args("submit", &[], day);
|
|
|
|
args.push(part.to_string());
|
|
|
|
args.push(result.to_string());
|
|
|
|
call_aoc_cli(&args)
|
|
|
|
}
|
|
|
|
|
2022-12-06 18:22:40 +01:00
|
|
|
fn get_input_path(day: u8) -> String {
|
2023-02-04 22:17:04 +00:00
|
|
|
let day_padded = format!("{day:02}");
|
|
|
|
format!("src/inputs/{day_padded}.txt")
|
2022-12-06 18:22:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_puzzle_path(day: u8) -> String {
|
2023-02-04 22:17:04 +00:00
|
|
|
let day_padded = format!("{day:02}");
|
|
|
|
format!("src/puzzles/{day_padded}.md")
|
2022-12-06 18:22:40 +01:00
|
|
|
}
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
fn get_year() -> Option<u16> {
|
|
|
|
match std::env::var("AOC_YEAR") {
|
|
|
|
Ok(x) => x.parse().ok().or(None),
|
|
|
|
Err(_) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> {
|
2022-12-06 18:22:40 +01:00
|
|
|
let mut cmd_args = args.to_vec();
|
|
|
|
|
2023-10-21 14:46:19 +02:00
|
|
|
if let Some(year) = get_year() {
|
2022-12-06 18:22:40 +01:00
|
|
|
cmd_args.push("--year".into());
|
|
|
|
cmd_args.push(year.to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd_args.append(&mut vec!["--day".into(), day.to_string(), command.into()]);
|
|
|
|
|
|
|
|
cmd_args
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call_aoc_cli(args: &[String]) -> Result<Output, AocCliError> {
|
|
|
|
if cfg!(debug_assertions) {
|
|
|
|
println!("Calling >aoc with: {}", args.join(" "));
|
|
|
|
}
|
|
|
|
|
|
|
|
Command::new("aoc")
|
|
|
|
.args(args)
|
|
|
|
.stdout(Stdio::inherit())
|
|
|
|
.stderr(Stdio::inherit())
|
|
|
|
.output()
|
|
|
|
.map_err(|_| AocCliError::CommandNotCallable)
|
|
|
|
}
|
|
|
|
}
|