parent
6cbc79760c
commit
66fdf36ff0
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 70 KiB |
@ -0,0 +1,6 @@ |
|||||||
|
[alias] |
||||||
|
scaffold = "run --bin scaffold -- " |
||||||
|
download = "run --bin download -- " |
||||||
|
|
||||||
|
solve = "run --bin" |
||||||
|
all = "run" |
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"name": "rust-devcontainer", |
||||||
|
"image": "mcr.microsoft.com/devcontainers/rust:latest", |
||||||
|
"postCreateCommand": "rustc --version", |
||||||
|
"remoteUser": "vscode" |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org |
||||||
|
root = true |
||||||
|
|
||||||
|
[*] |
||||||
|
indent_size = 4 |
||||||
|
indent_style = space |
||||||
|
end_of_line = lf |
||||||
|
charset = utf-8 |
||||||
|
insert_final_newline = true |
||||||
|
trim_trailing_whitespace = true |
||||||
|
|
||||||
|
[*.txt] |
||||||
|
insert_final_newline = false |
||||||
|
|
||||||
|
[*.md] |
||||||
|
trim_trailing_whitespace = false |
@ -0,0 +1,30 @@ |
|||||||
|
name: Continuous Integration |
||||||
|
|
||||||
|
on: push |
||||||
|
|
||||||
|
env: |
||||||
|
CARGO_TERM_COLOR: always |
||||||
|
|
||||||
|
jobs: |
||||||
|
check: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
name: Check |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v2 |
||||||
|
- name: cargo check |
||||||
|
run: cargo check |
||||||
|
test: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
name: Test |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v2 |
||||||
|
- name: cargo test |
||||||
|
run: cargo test |
||||||
|
# uncomment to enable clippy lints |
||||||
|
# clippy: |
||||||
|
# runs-on: ubuntu-latest |
||||||
|
# name: Lint (clippy) |
||||||
|
# steps: |
||||||
|
# - uses: actions/checkout@v2 |
||||||
|
# - name: cargo clippy |
||||||
|
# run: cargo clippy -- -D warnings |
@ -0,0 +1,20 @@ |
|||||||
|
# Generated by Cargo |
||||||
|
# will have compiled files and executables |
||||||
|
debug/ |
||||||
|
target/ |
||||||
|
|
||||||
|
# These are backup files generated by rustfmt |
||||||
|
**/*.rs.bk |
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information |
||||||
|
*.pdb |
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo |
||||||
|
|
||||||
|
/target |
||||||
|
|
||||||
|
# Advent of Code |
||||||
|
# @see https://old.reddit.com/r/adventofcode/comments/k99rod/sharing_input_data_were_we_requested_not_to/gf2ukkf/?context=3 |
||||||
|
/src/inputs |
||||||
|
!/src/inputs/.keep |
@ -0,0 +1,16 @@ |
|||||||
|
# This file is automatically @generated by Cargo. |
||||||
|
# It is not intended for manual editing. |
||||||
|
version = 3 |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "advent_of_code" |
||||||
|
version = "0.8.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 = "advent_of_code" |
||||||
|
version = "0.8.0" |
||||||
|
authors = ["Felix Spöttel <1682504+fspoettel@users.noreply.github.com>"] |
||||||
|
edition = "2021" |
||||||
|
default-run = "advent_of_code" |
||||||
|
publish = false |
||||||
|
# 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,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2021 Felix Spoettel |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,105 @@ |
|||||||
|
/* |
||||||
|
* This file contains template code. |
||||||
|
* There is no need to edit this file unless you want to change template functionality. |
||||||
|
*/ |
||||||
|
use std::io::Write; |
||||||
|
use std::path::PathBuf; |
||||||
|
use std::{env::temp_dir, io, process::Command}; |
||||||
|
use std::{fs, process}; |
||||||
|
|
||||||
|
struct Args { |
||||||
|
day: u8, |
||||||
|
year: Option<i16>, |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_args() -> Result<Args, pico_args::Error> { |
||||||
|
let mut args = pico_args::Arguments::from_env(); |
||||||
|
Ok(Args { |
||||||
|
day: args.free_from_str()?, |
||||||
|
year: args.opt_value_from_str(["-y", "--year"])?, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
fn remove_file(path: &PathBuf) { |
||||||
|
#[allow(unused_must_use)] |
||||||
|
{ |
||||||
|
fs::remove_file(path); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn exit_with_status(status: i32, path: &PathBuf) -> ! { |
||||||
|
remove_file(path); |
||||||
|
process::exit(status); |
||||||
|
} |
||||||
|
|
||||||
|
fn main() { |
||||||
|
// acquire a temp file path to write aoc-cli output to.
|
||||||
|
// aoc-cli expects this file not to be present - delete just in case.
|
||||||
|
let mut tmp_file_path = temp_dir(); |
||||||
|
tmp_file_path.push("aoc_input_tmp"); |
||||||
|
remove_file(&tmp_file_path); |
||||||
|
|
||||||
|
let args = match parse_args() { |
||||||
|
Ok(args) => args, |
||||||
|
Err(e) => { |
||||||
|
eprintln!("Failed to process arguments: {}", e); |
||||||
|
exit_with_status(1, &tmp_file_path); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let day_padded = format!("{:02}", args.day); |
||||||
|
let input_path = format!("src/inputs/{}.txt", day_padded); |
||||||
|
|
||||||
|
// check if aoc binary exists and is callable.
|
||||||
|
if Command::new("aoc").arg("-V").output().is_err() { |
||||||
|
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); |
||||||
|
exit_with_status(1, &tmp_file_path); |
||||||
|
} |
||||||
|
|
||||||
|
let mut cmd_args = vec![]; |
||||||
|
|
||||||
|
if let Some(year) = args.year { |
||||||
|
cmd_args.push("--year".into()); |
||||||
|
cmd_args.push(year.to_string()); |
||||||
|
} |
||||||
|
|
||||||
|
cmd_args.append(&mut vec![ |
||||||
|
"--input-file".into(), |
||||||
|
tmp_file_path.to_string_lossy().to_string(), |
||||||
|
"--day".into(), |
||||||
|
args.day.to_string(), |
||||||
|
"download".into(), |
||||||
|
]); |
||||||
|
|
||||||
|
println!("Downloading input with >aoc {}", cmd_args.join(" ")); |
||||||
|
|
||||||
|
match Command::new("aoc").args(cmd_args).output() { |
||||||
|
Ok(cmd_output) => { |
||||||
|
io::stdout() |
||||||
|
.write_all(&cmd_output.stdout) |
||||||
|
.expect("could not write cmd stdout to pipe."); |
||||||
|
io::stderr() |
||||||
|
.write_all(&cmd_output.stderr) |
||||||
|
.expect("could not write cmd stderr to pipe."); |
||||||
|
if !cmd_output.status.success() { |
||||||
|
exit_with_status(1, &tmp_file_path); |
||||||
|
} |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
eprintln!("failed to spawn aoc-cli: {}", e); |
||||||
|
exit_with_status(1, &tmp_file_path); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
match fs::copy(&tmp_file_path, &input_path) { |
||||||
|
Ok(_) => { |
||||||
|
println!("---"); |
||||||
|
println!("🎄 Successfully wrote input to \"{}\".", &input_path); |
||||||
|
exit_with_status(0, &tmp_file_path); |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
eprintln!("could not copy downloaded input to input file: {}", e); |
||||||
|
exit_with_status(1, &tmp_file_path); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* 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 = &advent_of_code::read_file("inputs", DAY); |
||||||
|
advent_of_code::solve!(1, part_one, input); |
||||||
|
advent_of_code::solve!(2, part_two, input); |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_part_one() { |
||||||
|
let input = advent_of_code::read_file("examples", DAY); |
||||||
|
assert_eq!(part_one(&input), None); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_part_two() { |
||||||
|
let input = advent_of_code::read_file("examples", 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 scaffold 7`"); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let day_padded = format!("{:02}", day); |
||||||
|
|
||||||
|
let input_path = format!("src/inputs/{}.txt", day_padded); |
||||||
|
let example_path = format!("src/examples/{}.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,4 @@ |
|||||||
|
/* |
||||||
|
* Use this file if you want to extract helpers from your solutions. |
||||||
|
* Example import from this file: `use advent_of_code::helpers::example_fn;`. |
||||||
|
*/ |
@ -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 advent_of_code::{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 advent_of_code::{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 { |
||||||
|
advent_of_code::parse_exec_time(&output) |
||||||
|
} |
||||||
|
}) |
||||||
|
.sum(); |
||||||
|
|
||||||
|
println!( |
||||||
|
"{}Total:{} {}{:.2}ms{}", |
||||||
|
ANSI_BOLD, ANSI_RESET, ANSI_ITALIC, total, ANSI_RESET |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue