From c6e8a2d19f1d1a51409f3b0fd5cff10c5f8f5c5b Mon Sep 17 00:00:00 2001 From: Andrew Coleman Date: Fri, 9 Feb 2024 16:47:40 -0500 Subject: [PATCH] switch to different AoC template --- 2023/.cargo/config.toml | 12 + 2023/.editorconfig | 17 + 2023/.gitignore | 31 +- 2023/Cargo.lock | 525 +++++++++++++++++++++---- 2023/Cargo.toml | 37 +- 2023/LICENSE | 21 + 2023/README.md | 1 + 2023/data/examples/.keep | 0 2023/data/examples/01-2.txt | 7 + 2023/data/examples/01.txt | 4 + 2023/data/examples/02.txt | 5 + 2023/data/examples/03.txt | 10 + 2023/data/examples/04.txt | 6 + 2023/data/examples/05.txt | 33 ++ 2023/data/examples/06.txt | 2 + 2023/data/examples/07.txt | 5 + 2023/data/examples/08-2.txt | 10 + 2023/data/examples/08.txt | 9 + 2023/data/examples/09.txt | 3 + 2023/data/examples/10-2.txt | 5 + 2023/data/examples/10-3.txt | 9 + 2023/data/examples/10-4.txt | 10 + 2023/data/examples/10.txt | 5 + 2023/data/examples/11.txt | 10 + 2023/data/examples/12.txt | 6 + 2023/data/examples/13-2.txt | 41 ++ 2023/data/examples/13.txt | 16 + 2023/data/examples/14.txt | 10 + 2023/data/examples/15.txt | 1 + 2023/data/examples/16.txt | 10 + 2023/data/examples/17.txt | 13 + 2023/data/examples/18.txt | 14 + 2023/data/examples/19.txt | 17 + 2023/data/examples/20-2.txt | 5 + 2023/data/examples/20.txt | 5 + 2023/data/examples/21.txt | 11 + 2023/data/examples/22.txt | 0 2023/data/examples/23.txt | 0 2023/data/examples/24.txt | 0 2023/data/examples/25.txt | 0 2023/data/inputs/.keep | 0 2023/data/puzzles/.keep | 0 2023/src/bin/.keep | 0 2023/src/{day01.rs => bin/01.rs} | 46 +-- 2023/src/{day02.rs => bin/02.rs} | 32 +- 2023/src/{day03.rs => bin/03.rs} | 41 +- 2023/src/{day04.rs => bin/04.rs} | 36 +- 2023/src/{day05.rs => bin/05.rs} | 62 +-- 2023/src/{day06.rs => bin/06.rs} | 35 +- 2023/src/{day07.rs => bin/07.rs} | 36 +- 2023/src/{day08.rs => bin/08.rs} | 60 +-- 2023/src/{day09.rs => bin/09.rs} | 37 +- 2023/src/{day10.rs => bin/10.rs} | 83 ++-- 2023/src/{day11.rs => bin/11.rs} | 47 +-- 2023/src/{day12.rs => bin/12.rs} | 48 +-- 2023/src/{day13.rs => bin/13.rs} | 106 ++--- 2023/src/{day14.rs => bin/14.rs} | 45 +-- 2023/src/{day15.rs => bin/15.rs} | 26 +- 2023/src/{day16.rs => bin/16.rs} | 48 +-- 2023/src/{day17.rs => bin/17.rs} | 39 +- 2023/src/{day18.rs => bin/18.rs} | 42 +- 2023/src/{day19.rs => bin/19.rs} | 42 +- 2023/src/{day20.rs => bin/20.rs} | 43 +- 2023/src/{day21.rs => bin/21.rs} | 43 +- 2023/src/bin/22.rs | 26 ++ 2023/src/bin/23.rs | 26 ++ 2023/src/bin/24.rs | 26 ++ 2023/src/bin/25.rs | 26 ++ 2023/src/lib.rs | 26 +- 2023/src/main.rs | 141 ++++++- 2023/src/template.txt | 26 ++ 2023/src/template/aoc_cli.rs | 125 ++++++ 2023/src/template/commands/all.rs | 5 + 2023/src/template/commands/download.rs | 14 + 2023/src/template/commands/mod.rs | 6 + 2023/src/template/commands/read.rs | 15 + 2023/src/template/commands/scaffold.rs | 69 ++++ 2023/src/template/commands/solve.rs | 34 ++ 2023/src/template/commands/time.rs | 40 ++ 2023/src/template/day.rs | 192 +++++++++ 2023/src/template/mod.rs | 68 ++++ 2023/src/template/readme_benchmarks.rs | 182 +++++++++ 2023/src/template/run_multi.rs | 256 ++++++++++++ 2023/src/template/runner.rs | 174 ++++++++ 2023/src/template/timings.rs | 391 ++++++++++++++++++ 85 files changed, 3045 insertions(+), 746 deletions(-) create mode 100644 2023/.cargo/config.toml create mode 100644 2023/.editorconfig create mode 100644 2023/LICENSE create mode 100644 2023/README.md create mode 100644 2023/data/examples/.keep create mode 100644 2023/data/examples/01-2.txt create mode 100644 2023/data/examples/01.txt create mode 100644 2023/data/examples/02.txt create mode 100644 2023/data/examples/03.txt create mode 100644 2023/data/examples/04.txt create mode 100644 2023/data/examples/05.txt create mode 100644 2023/data/examples/06.txt create mode 100644 2023/data/examples/07.txt create mode 100644 2023/data/examples/08-2.txt create mode 100644 2023/data/examples/08.txt create mode 100644 2023/data/examples/09.txt create mode 100644 2023/data/examples/10-2.txt create mode 100644 2023/data/examples/10-3.txt create mode 100644 2023/data/examples/10-4.txt create mode 100644 2023/data/examples/10.txt create mode 100644 2023/data/examples/11.txt create mode 100644 2023/data/examples/12.txt create mode 100644 2023/data/examples/13-2.txt create mode 100644 2023/data/examples/13.txt create mode 100644 2023/data/examples/14.txt create mode 100644 2023/data/examples/15.txt create mode 100644 2023/data/examples/16.txt create mode 100644 2023/data/examples/17.txt create mode 100644 2023/data/examples/18.txt create mode 100644 2023/data/examples/19.txt create mode 100644 2023/data/examples/20-2.txt create mode 100644 2023/data/examples/20.txt create mode 100644 2023/data/examples/21.txt create mode 100644 2023/data/examples/22.txt create mode 100644 2023/data/examples/23.txt create mode 100644 2023/data/examples/24.txt create mode 100644 2023/data/examples/25.txt create mode 100644 2023/data/inputs/.keep create mode 100644 2023/data/puzzles/.keep create mode 100644 2023/src/bin/.keep rename 2023/src/{day01.rs => bin/01.rs} (74%) rename 2023/src/{day02.rs => bin/02.rs} (77%) rename 2023/src/{day03.rs => bin/03.rs} (85%) rename 2023/src/{day04.rs => bin/04.rs} (75%) rename 2023/src/{day05.rs => bin/05.rs} (80%) rename 2023/src/{day06.rs => bin/06.rs} (73%) rename 2023/src/{day07.rs => bin/07.rs} (93%) rename 2023/src/{day08.rs => bin/08.rs} (74%) rename 2023/src/{day09.rs => bin/09.rs} (51%) rename 2023/src/{day10.rs => bin/10.rs} (82%) rename 2023/src/{day11.rs => bin/11.rs} (82%) rename 2023/src/{day12.rs => bin/12.rs} (86%) rename 2023/src/{day13.rs => bin/13.rs} (81%) rename 2023/src/{day14.rs => bin/14.rs} (87%) rename 2023/src/{day15.rs => bin/15.rs} (70%) rename 2023/src/{day16.rs => bin/16.rs} (85%) rename 2023/src/{day17.rs => bin/17.rs} (83%) rename 2023/src/{day18.rs => bin/18.rs} (77%) rename 2023/src/{day19.rs => bin/19.rs} (88%) rename 2023/src/{day20.rs => bin/20.rs} (89%) rename 2023/src/{day21.rs => bin/21.rs} (79%) create mode 100644 2023/src/bin/22.rs create mode 100644 2023/src/bin/23.rs create mode 100644 2023/src/bin/24.rs create mode 100644 2023/src/bin/25.rs create mode 100644 2023/src/template.txt create mode 100644 2023/src/template/aoc_cli.rs create mode 100644 2023/src/template/commands/all.rs create mode 100644 2023/src/template/commands/download.rs create mode 100644 2023/src/template/commands/mod.rs create mode 100644 2023/src/template/commands/read.rs create mode 100644 2023/src/template/commands/scaffold.rs create mode 100644 2023/src/template/commands/solve.rs create mode 100644 2023/src/template/commands/time.rs create mode 100644 2023/src/template/day.rs create mode 100644 2023/src/template/mod.rs create mode 100644 2023/src/template/readme_benchmarks.rs create mode 100644 2023/src/template/run_multi.rs create mode 100644 2023/src/template/runner.rs create mode 100644 2023/src/template/timings.rs diff --git a/2023/.cargo/config.toml b/2023/.cargo/config.toml new file mode 100644 index 0000000..66b873e --- /dev/null +++ b/2023/.cargo/config.toml @@ -0,0 +1,12 @@ +[alias] +today = "run --quiet --release --features today -- today" +scaffold = "run --quiet --release -- scaffold" +download = "run --quiet --release -- download" +read = "run --quiet --release -- read" + +solve = "run --quiet --release -- solve" +all = "run --quiet --release -- all" +time = "run --quiet --release -- time" + +[env] +AOC_YEAR = "2023" diff --git a/2023/.editorconfig b/2023/.editorconfig new file mode 100644 index 0000000..560e94b --- /dev/null +++ b/2023/.editorconfig @@ -0,0 +1,17 @@ +# 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 +trim_trailing_whitespace = false + +[*.md] +trim_trailing_whitespace = false diff --git a/2023/.gitignore b/2023/.gitignore index 3f9177e..216820d 100644 --- a/2023/.gitignore +++ b/2023/.gitignore @@ -1 +1,30 @@ -input +# 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 + +data/inputs/* +!data/inputs/.keep +data/puzzles/* +!data/puzzles/.keep + +# Dhat +dhat-heap.json + +# Benchmarks + +data/timings.json diff --git a/2023/Cargo.lock b/2023/Cargo.lock index 055dce3..7ebace4 100644 --- a/2023/Cargo.lock +++ b/2023/Cargo.lock @@ -2,6 +2,34 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "advent_of_code" +version = "0.11.0" +dependencies = [ + "aoc-parse", + "chrono", + "dhat", + "num", + "pathfinding", + "pico-args", + "tinyjson", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -11,6 +39,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "aoc-parse" version = "0.2.18" @@ -25,52 +68,74 @@ dependencies = [ "tuple_utils", ] -[[package]] -name = "aoc-runner" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21ef9204ad206a5a3e918e9920da04e1118ad91ce4f23570be964b9d6b9dfcb" - -[[package]] -name = "aoc-runner-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8b944269d3fee645d281b1335e1797044db497bb02d0098cc3fdb8900069cc" -dependencies = [ - "aoc-runner-internal", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "aoc-runner-internal" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274b0ba7f3669a45ec0aaacf94eb032a749de880ab776091576cca94037c9982" -dependencies = [ - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "aoc2023" -version = "0.1.0" -dependencies = [ - "aoc-parse", - "aoc-runner", - "aoc-runner-derive", - "num", - "pathfinding", -] - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "deprecate-until" version = "0.1.1" @@ -80,7 +145,23 @@ dependencies = [ "proc-macro2", "quote", "semver", - "syn 2.0.39", + "syn", +] + +[[package]] +name = "dhat" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2aaf837aaf456f6706cb46386ba8dffd4013a757e36f4ea05c20dd46b209a3" +dependencies = [ + "backtrace", + "lazy_static", + "mintex", + "parking_lot", + "rustc-hash", + "serde", + "serde_json", + "thousands", ] [[package]] @@ -95,6 +176,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "hashbrown" version = "0.14.3" @@ -102,10 +189,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] -name = "indexmap" -version = "2.1.0" +name = "iana-time-zone" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown", @@ -126,18 +236,68 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mintex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7c5ba1c3b5a23418d7bbf98c71c3d4946a0125002129231da8d6b723d559cb" +dependencies = [ + "once_cell", + "sys-info", +] + [[package]] name = "num" version = "0.4.1" @@ -165,28 +325,27 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -215,10 +374,48 @@ dependencies = [ ] [[package]] -name = "pathfinding" -version = "4.8.0" +name = "object" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f4a3f5089b981000cb50ec24320faf7a19649a45e8730e4adf49f78f066528" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pathfinding" +version = "4.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35df0d074a99be583b76dc771329be9f7be45ebae444d601285dc7a094c2ac0a" dependencies = [ "deprecate-until", "fixedbitset", @@ -230,28 +427,43 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.70" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] -name = "regex" -version = "1.10.2" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -261,9 +473,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -276,6 +488,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -289,10 +507,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] -name = "semver" -version = "1.0.20" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" @@ -311,7 +535,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -326,10 +550,16 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.109" +name = "smallvec" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -337,36 +567,47 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.39" +name = "sys-info" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "cc", + "libc", ] [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "tinyjson" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a" + [[package]] name = "tuple_utils" version = "0.4.0" @@ -378,3 +619,123 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/2023/Cargo.toml b/2023/Cargo.toml index f865997..42368ae 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -1,18 +1,33 @@ [package] -name = "aoc2023" -version = "0.1.0" +name = "advent_of_code" +version = "0.11.0" +authors = ["Andrew Coleman "] 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 [lib] -name = "aoc2023" -path = "src/lib.rs" +doctest = false + +[profile.dhat] +inherits = "release" +debug = 1 + +[features] +dhat-heap = ["dhat"] +today = ["chrono"] +test_lib = [] [dependencies] -aoc-runner = "0.3.0" -aoc-runner-derive = "0.3.0" -aoc-parse = "0.2.17" -num = "0.4" -pathfinding = "4.8.0" +aoc-parse = "0.2.18" -[profile.release] -incremental = true +# Template dependencies +chrono = { version = "0.4.31", optional = true } +dhat = { version = "0.3.2", optional = true } +num = "0.4.1" +pathfinding = "4.8.2" +pico-args = "0.5.0" +tinyjson = "2.5.1" + +# Solution dependencies diff --git a/2023/LICENSE b/2023/LICENSE new file mode 100644 index 0000000..b97fd05 --- /dev/null +++ b/2023/LICENSE @@ -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. diff --git a/2023/README.md b/2023/README.md new file mode 100644 index 0000000..d788bbc --- /dev/null +++ b/2023/README.md @@ -0,0 +1 @@ +Template from https://github.com/fspoettel/advent-of-code-rust diff --git a/2023/data/examples/.keep b/2023/data/examples/.keep new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/examples/01-2.txt b/2023/data/examples/01-2.txt new file mode 100644 index 0000000..41aa89c --- /dev/null +++ b/2023/data/examples/01-2.txt @@ -0,0 +1,7 @@ +two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen diff --git a/2023/data/examples/01.txt b/2023/data/examples/01.txt new file mode 100644 index 0000000..7bbc69a --- /dev/null +++ b/2023/data/examples/01.txt @@ -0,0 +1,4 @@ +1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet diff --git a/2023/data/examples/02.txt b/2023/data/examples/02.txt new file mode 100644 index 0000000..295c36d --- /dev/null +++ b/2023/data/examples/02.txt @@ -0,0 +1,5 @@ +Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green diff --git a/2023/data/examples/03.txt b/2023/data/examples/03.txt new file mode 100644 index 0000000..b20187f --- /dev/null +++ b/2023/data/examples/03.txt @@ -0,0 +1,10 @@ +467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. diff --git a/2023/data/examples/04.txt b/2023/data/examples/04.txt new file mode 100644 index 0000000..9bdb874 --- /dev/null +++ b/2023/data/examples/04.txt @@ -0,0 +1,6 @@ +Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 +Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 +Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 +Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 +Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 +Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 diff --git a/2023/data/examples/05.txt b/2023/data/examples/05.txt new file mode 100644 index 0000000..f756727 --- /dev/null +++ b/2023/data/examples/05.txt @@ -0,0 +1,33 @@ +seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4 diff --git a/2023/data/examples/06.txt b/2023/data/examples/06.txt new file mode 100644 index 0000000..28f5ae9 --- /dev/null +++ b/2023/data/examples/06.txt @@ -0,0 +1,2 @@ +Time: 7 15 30 +Distance: 9 40 200 diff --git a/2023/data/examples/07.txt b/2023/data/examples/07.txt new file mode 100644 index 0000000..e3500c3 --- /dev/null +++ b/2023/data/examples/07.txt @@ -0,0 +1,5 @@ +32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483 diff --git a/2023/data/examples/08-2.txt b/2023/data/examples/08-2.txt new file mode 100644 index 0000000..5b3fa58 --- /dev/null +++ b/2023/data/examples/08-2.txt @@ -0,0 +1,10 @@ +LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX) diff --git a/2023/data/examples/08.txt b/2023/data/examples/08.txt new file mode 100644 index 0000000..9029a1b --- /dev/null +++ b/2023/data/examples/08.txt @@ -0,0 +1,9 @@ +RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ) diff --git a/2023/data/examples/09.txt b/2023/data/examples/09.txt new file mode 100644 index 0000000..539a763 --- /dev/null +++ b/2023/data/examples/09.txt @@ -0,0 +1,3 @@ +0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45 diff --git a/2023/data/examples/10-2.txt b/2023/data/examples/10-2.txt new file mode 100644 index 0000000..682499d --- /dev/null +++ b/2023/data/examples/10-2.txt @@ -0,0 +1,5 @@ +..F7. +.FJ|. +SJ.L7 +|F--J +LJ... diff --git a/2023/data/examples/10-3.txt b/2023/data/examples/10-3.txt new file mode 100644 index 0000000..d6c0f21 --- /dev/null +++ b/2023/data/examples/10-3.txt @@ -0,0 +1,9 @@ +.......... +.S------7. +.|F----7|. +.||....||. +.||....||. +.|L-7F-J|. +.|..||..|. +.L--JL--J. +.......... diff --git a/2023/data/examples/10-4.txt b/2023/data/examples/10-4.txt new file mode 100644 index 0000000..adaae96 --- /dev/null +++ b/2023/data/examples/10-4.txt @@ -0,0 +1,10 @@ +.F----7F7F7F7F-7.... +.|F--7||||||||FJ.... +.||.FJ||||||||L7.... +FJL7L7LJLJ||LJ.L-7.. +L--J.L7...LJS7F-7L7. +....F-J..F7FJ|L7L7L7 +....L7.F7||L7|.L7L7| +.....|FJLJ|FJ|F7|.LJ +....FJL-7.||.||||... +....L---J.LJ.LJLJ... diff --git a/2023/data/examples/10.txt b/2023/data/examples/10.txt new file mode 100644 index 0000000..7650925 --- /dev/null +++ b/2023/data/examples/10.txt @@ -0,0 +1,5 @@ +..... +.S-7. +.|.|. +.L-J. +..... diff --git a/2023/data/examples/11.txt b/2023/data/examples/11.txt new file mode 100644 index 0000000..986aad4 --- /dev/null +++ b/2023/data/examples/11.txt @@ -0,0 +1,10 @@ +...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#..... diff --git a/2023/data/examples/12.txt b/2023/data/examples/12.txt new file mode 100644 index 0000000..e925935 --- /dev/null +++ b/2023/data/examples/12.txt @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 diff --git a/2023/data/examples/13-2.txt b/2023/data/examples/13-2.txt new file mode 100644 index 0000000..4879428 --- /dev/null +++ b/2023/data/examples/13-2.txt @@ -0,0 +1,41 @@ + +#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# + +.#.##.#.# +.##..##.. +.#.##.#.. +#......## +#......## +.#.##.#.. +.##..##.# + +#..#....# +###..##.. +.##.##### +.##.##### +###..##.. +#..#....# +#..##...# + +#.##..##. +..#.##.#. +##..#...# +##...#..# +..#.##.#. +..##..##. +#.#.##.#. + diff --git a/2023/data/examples/13.txt b/2023/data/examples/13.txt new file mode 100644 index 0000000..b056c34 --- /dev/null +++ b/2023/data/examples/13.txt @@ -0,0 +1,16 @@ +#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# + diff --git a/2023/data/examples/14.txt b/2023/data/examples/14.txt new file mode 100644 index 0000000..5a24dce --- /dev/null +++ b/2023/data/examples/14.txt @@ -0,0 +1,10 @@ +O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#.... diff --git a/2023/data/examples/15.txt b/2023/data/examples/15.txt new file mode 100644 index 0000000..4f58f74 --- /dev/null +++ b/2023/data/examples/15.txt @@ -0,0 +1 @@ +rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 diff --git a/2023/data/examples/16.txt b/2023/data/examples/16.txt new file mode 100644 index 0000000..d6805ce --- /dev/null +++ b/2023/data/examples/16.txt @@ -0,0 +1,10 @@ +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... diff --git a/2023/data/examples/17.txt b/2023/data/examples/17.txt new file mode 100644 index 0000000..f400d6e --- /dev/null +++ b/2023/data/examples/17.txt @@ -0,0 +1,13 @@ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 diff --git a/2023/data/examples/18.txt b/2023/data/examples/18.txt new file mode 100644 index 0000000..fc7612e --- /dev/null +++ b/2023/data/examples/18.txt @@ -0,0 +1,14 @@ +R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3) diff --git a/2023/data/examples/19.txt b/2023/data/examples/19.txt new file mode 100644 index 0000000..e5b5d64 --- /dev/null +++ b/2023/data/examples/19.txt @@ -0,0 +1,17 @@ +px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013} diff --git a/2023/data/examples/20-2.txt b/2023/data/examples/20-2.txt new file mode 100644 index 0000000..2738ceb --- /dev/null +++ b/2023/data/examples/20-2.txt @@ -0,0 +1,5 @@ +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output diff --git a/2023/data/examples/20.txt b/2023/data/examples/20.txt new file mode 100644 index 0000000..2dc1bab --- /dev/null +++ b/2023/data/examples/20.txt @@ -0,0 +1,5 @@ +broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a diff --git a/2023/data/examples/21.txt b/2023/data/examples/21.txt new file mode 100644 index 0000000..9e1d842 --- /dev/null +++ b/2023/data/examples/21.txt @@ -0,0 +1,11 @@ +........... +.....###.#. +.###.##..#. +..#.#...#.. +....#.#.... +.##..S####. +.##..#...#. +.......##.. +.##.#.####. +.##..##.##. +........... diff --git a/2023/data/examples/22.txt b/2023/data/examples/22.txt new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/examples/23.txt b/2023/data/examples/23.txt new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/examples/24.txt b/2023/data/examples/24.txt new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/examples/25.txt b/2023/data/examples/25.txt new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/inputs/.keep b/2023/data/inputs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/2023/data/puzzles/.keep b/2023/data/puzzles/.keep new file mode 100644 index 0000000..e69de29 diff --git a/2023/src/bin/.keep b/2023/src/bin/.keep new file mode 100644 index 0000000..e69de29 diff --git a/2023/src/day01.rs b/2023/src/bin/01.rs similarity index 74% rename from 2023/src/day01.rs rename to 2023/src/bin/01.rs index 2ee7616..702cd67 100644 --- a/2023/src/day01.rs +++ b/2023/src/bin/01.rs @@ -1,4 +1,4 @@ -use aoc_runner_derive::aoc; +advent_of_code::solution!(1); fn parse(input: &str) -> Vec { input @@ -16,15 +16,13 @@ fn parse(input: &str) -> Vec { .collect() } -#[aoc(day1, part1)] -fn part1_sum(input: &str) -> u32 { +pub fn part_one(input: &str) -> Option { let lines = parse(input); - lines.iter().sum() + Some(lines.iter().sum()) } -#[aoc(day1, part2)] -fn part2_sum(input: &str) -> u32 { - sub_letters_for_digits(input).iter().sum() +pub fn part_two(input: &str) -> Option { + Some(sub_letters_for_digits(input).iter().sum()) } fn char_to_num(c: char) -> Option { @@ -101,37 +99,15 @@ fn sub_letters_for_digits(input: &str) -> Vec { mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#" -1abc2 -pqr3stu8vwx -a1b2c3d4e5f -treb7uchet -"#; - #[test] - fn sample_data() { - let expected = [12, 38, 15, 77]; - let data = parse(SAMPLE_DATA); - assert_eq!(data, expected); - let part1 = part1_sum(SAMPLE_DATA); - assert_eq!(part1, 142); + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(142)); } - const SAMPLE_DATA2: &'static str = r#" -two1nine -eightwothree -abcone2threexyz -xtwone3four -4nineeightseven2 -zoneight234 -7pqrstsixteen -"#; - #[test] - fn sample_data_part2() { - let data = sub_letters_for_digits(SAMPLE_DATA2); - assert_eq!(data, [29, 83, 13, 24, 42, 14, 76]); - let part2 = part2_sum(SAMPLE_DATA2); - assert_eq!(part2, 281); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY, 2)); + assert_eq!(result, Some(281)); } } diff --git a/2023/src/day02.rs b/2023/src/bin/02.rs similarity index 77% rename from 2023/src/day02.rs rename to 2023/src/bin/02.rs index af15bdf..48d5f46 100644 --- a/2023/src/day02.rs +++ b/2023/src/bin/02.rs @@ -1,4 +1,4 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(2); const MAX_RED: u32 = 12; const MAX_GREEN: u32 = 13; @@ -46,7 +46,6 @@ impl Game { } } -#[aoc_generator(day2)] fn parse(input: &str) -> Vec { input .lines() @@ -81,17 +80,32 @@ fn parse(input: &str) -> Vec { .collect() } -#[aoc(day2, part1)] -fn part1_valid_game_sum(input: &[Game]) -> u32 { - input.iter().filter_map(|game| game.part1_possible()).sum() +pub fn part_one(input: &str) -> Option { + Some( + parse(input) + .iter() + .filter_map(|game| game.part1_possible()) + .sum(), + ) } -#[aoc(day2, part2)] -fn part2_power_sum(input: &[Game]) -> u32 { - input.iter().map(|game| game.part2_power()).sum() +pub fn part_two(input: &str) -> Option { + Some(parse(input).iter().map(|game| game.part2_power()).sum()) } #[cfg(test)] mod tests { - // use super::*; + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(8)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(2286)); + } } diff --git a/2023/src/day03.rs b/2023/src/bin/03.rs similarity index 85% rename from 2023/src/day03.rs rename to 2023/src/bin/03.rs index bafe835..4b02b79 100644 --- a/2023/src/day03.rs +++ b/2023/src/bin/03.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(3); + use std::collections::BTreeMap; type PartList = BTreeMap<(usize, usize, char), Vec>; @@ -16,7 +17,6 @@ fn parse(input: &str) -> Vec> { .collect() } -#[aoc_generator(day3)] fn associate_parts(input: &str) -> PartList { let parsed_input = parse(input); let mut result: PartList = BTreeMap::new(); @@ -87,46 +87,37 @@ fn associate_parts(input: &str) -> PartList { result } -#[aoc(day3, part1)] -fn part1(input: &PartList) -> u32 { +pub fn part_one(input: &str) -> Option { let mut sum: u32 = 0; - for (_coord, plist) in input.iter() { + for (_coord, plist) in associate_parts(input).iter() { sum += plist.iter().sum::(); } - sum + Some(sum) } -#[aoc(day3, part2)] -fn part2(input: &PartList) -> u32 { +pub fn part_two(input: &str) -> Option { let mut sum: u32 = 0; - for (coord, plist) in input.iter() { + for (coord, plist) in associate_parts(input).iter() { if coord.2 == '*' && plist.len() == 2 { sum += plist.iter().product::(); } } - sum + Some(sum) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"467..114.. -...*...... -..35..633. -......#... -617*...... -.....+.58. -..592..... -......755. -...$.*.... -.664.598.. -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(4361)); + } #[test] - fn sample_data() { - let parts = associate_parts(&SAMPLE_DATA); - assert_eq!(part1(&parts), 4361); - assert_eq!(part2(&parts), 467835); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(467835)); } } diff --git a/2023/src/day04.rs b/2023/src/bin/04.rs similarity index 75% rename from 2023/src/day04.rs rename to 2023/src/bin/04.rs index 22dda55..8ba68da 100644 --- a/2023/src/day04.rs +++ b/2023/src/bin/04.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(4); + use std::collections::HashSet; struct Card { @@ -15,7 +16,6 @@ impl Card { } } -#[aoc_generator(day4)] fn parse(input: &str) -> Vec { input .lines() @@ -54,11 +54,10 @@ fn parse(input: &str) -> Vec { .collect() } -#[aoc(day4, part1)] -fn part1(input: &[Card]) -> i32 { +pub fn part_one(input: &str) -> Option { let mut sum = 0; let base: i32 = 2; - for c in input.iter() { + for c in parse(input).iter() { let m = c.matching_numbers(); if m.is_empty() { continue; @@ -68,12 +67,12 @@ fn part1(input: &[Card]) -> i32 { sum += p; } } - sum + Some(sum.try_into().unwrap()) } -#[aoc(day4, part2)] -fn part2(input: &[Card]) -> u32 { +pub fn part_two(input_str: &str) -> Option { let mut cards: [u32; 202] = [1; 202]; + let input = parse(input_str); for i in input.len()..cards.len() { cards[i] = 0; } @@ -88,25 +87,22 @@ fn part2(input: &[Card]) -> u32 { cards[dest] += cards[cur]; } } - cards.iter().sum() + Some(cards.iter().sum()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 -Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 -Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 -Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 -Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 -Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(13)); + } #[test] - fn sample_data() { - let cards = parse(&SAMPLE_DATA); - assert_eq!(part1(&cards), 13); - assert_eq!(part2(&cards), 30); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(30)); } } diff --git a/2023/src/day05.rs b/2023/src/bin/05.rs similarity index 80% rename from 2023/src/day05.rs rename to 2023/src/bin/05.rs index 88e29f9..fd3a095 100644 --- a/2023/src/day05.rs +++ b/2023/src/bin/05.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(5); + use std::ops::RangeInclusive; use std::str::Lines; @@ -43,7 +44,6 @@ fn next_rangemap(lines: &mut Lines) -> Vec { rm } -#[aoc_generator(day5)] fn parse(input: &str) -> Almanac { let mut lines = input.lines(); let seeds = lines.next().unwrap().split(": ").collect::>()[1] @@ -102,13 +102,13 @@ fn min_location(seeds: &Vec, input: &Almanac) -> u64 { *locations.iter().min().unwrap() } -#[aoc(day5, part1)] -fn part1(input: &Almanac) -> u64 { - min_location(&input.seeds, input) +pub fn part_one(input_str: &str) -> Option { + let input = parse(input_str); + Some(min_location(&input.seeds, &input)) } -#[aoc(day5, part2, bf)] -fn part2(input: &Almanac) -> u64 { +pub fn part_two(input_str: &str) -> Option { + let input = parse(input_str); let mut seeds: Vec = vec![]; let mut index = 0; while index < input.seeds.len() { @@ -119,52 +119,22 @@ fn part2(input: &Almanac) -> u64 { seeds.push(n); } } - min_location(&seeds, input) + Some(min_location(&seeds, &input)) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"seeds: 79 14 55 13 - -seed-to-soil map: -50 98 2 -52 50 48 - -soil-to-fertilizer map: -0 15 37 -37 52 2 -39 0 15 - -fertilizer-to-water map: -49 53 8 -0 11 42 -42 0 7 -57 7 4 - -water-to-light map: -88 18 7 -18 25 70 - -light-to-temperature map: -45 77 23 -81 45 19 -68 64 13 - -temperature-to-humidity map: -0 69 1 -1 0 69 - -humidity-to-location map: -60 56 37 -56 93 4 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(35)); + } #[test] - fn sample_data() { - let maps = parse(&SAMPLE_DATA); - assert_eq!(part1(&maps), 35); - assert_eq!(part2(&maps), 46); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(46)); } } diff --git a/2023/src/day06.rs b/2023/src/bin/06.rs similarity index 73% rename from 2023/src/day06.rs rename to 2023/src/bin/06.rs index cbd40dc..c3022bc 100644 --- a/2023/src/day06.rs +++ b/2023/src/bin/06.rs @@ -1,4 +1,4 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(6); struct Race { time: u64, @@ -17,7 +17,6 @@ impl Race { } } -#[aoc_generator(day6)] fn parse(input: &str) -> Vec { let mut lines = input.lines(); let times: Vec = lines.next().unwrap().split(": ").collect::>()[1] @@ -39,19 +38,18 @@ fn parse(input: &str) -> Vec { .collect() } -#[aoc(day6, part1)] -fn part1(input: &[Race]) -> u64 { - input +pub fn part_one(input: &str) -> Option { + Some(parse(input) .iter() .map(|race| race.winning_times().len() as u64) .collect::>() .iter() - .product() + .product()) } -#[aoc(day6, part2)] -fn part2(input: &[Race]) -> u64 { - let time = input +pub fn part_two(input: &str) -> Option { + let races = parse(input); + let time = races .iter() .map(|race| race.time) .collect::>() @@ -61,7 +59,7 @@ fn part2(input: &[Race]) -> u64 { .join("") .parse::() .unwrap(); - let distance = input + let distance = races .iter() .map(|race| race.distance) .collect::>() @@ -72,21 +70,22 @@ fn part2(input: &[Race]) -> u64 { .parse::() .unwrap(); let race = Race { time, distance }; - race.winning_times().len() as u64 + Some(race.winning_times().len() as u64) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"Time: 7 15 30 -Distance: 9 40 200 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(288)); + } #[test] - fn sample_data() { - let races = parse(&SAMPLE_DATA); - assert_eq!(part1(&races), 288); - assert_eq!(part2(&races), 71503); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(71503)); } } diff --git a/2023/src/day07.rs b/2023/src/bin/07.rs similarity index 93% rename from 2023/src/day07.rs rename to 2023/src/bin/07.rs index 8d83cf8..e49f6c4 100644 --- a/2023/src/day07.rs +++ b/2023/src/bin/07.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(7); + use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet}; use std::str::FromStr; @@ -218,7 +219,6 @@ struct Part2Hand { hand_kind: HandKinds, } -#[aoc_generator(day7)] fn parse(input: &str) -> Vec { input .lines() @@ -254,21 +254,19 @@ fn parse(input: &str) -> Vec { .collect() } -#[aoc(day7, part1)] -fn part1(input: &[Hand]) -> u64 { +pub fn part_one(input: &str) -> Option { let mut sum: u64 = 0; - let mut hands = input.iter().map(|h| h.clone()).collect::>(); + let mut hands = parse(input).iter().map(|h| h.clone()).collect::>(); hands.sort(); for (index, hand) in hands.iter().rev().enumerate() { sum += hand.bid * (index as u64 + 1); } - sum + Some(sum.try_into().unwrap()) } -#[aoc(day7, part2)] -fn part2(input: &[Hand]) -> u64 { +pub fn part_two(input: &str) -> Option { let mut sum: u64 = 0; - let mut hands = input + let mut hands = parse(input) .iter() .map(|h| { let bid = h.bid; @@ -329,24 +327,22 @@ fn part2(input: &[Hand]) -> u64 { for (index, hand) in hands.iter().rev().enumerate() { sum += hand.bid * (index as u64 + 1); } - sum + Some(sum.try_into().unwrap()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"32T3K 765 -T55J5 684 -KK677 28 -KTJJT 220 -QQQJA 483 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(6440)); + } #[test] - fn sample_data() { - let cards = parse(&SAMPLE_DATA); - assert_eq!(part1(&cards), 6440); - assert_eq!(part2(&cards), 5905); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(5905)); } } diff --git a/2023/src/day08.rs b/2023/src/bin/08.rs similarity index 74% rename from 2023/src/day08.rs rename to 2023/src/bin/08.rs index c051d75..c9f8e7e 100644 --- a/2023/src/day08.rs +++ b/2023/src/bin/08.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::aoc; +advent_of_code::solution!(8); + use num::integer::lcm; use std::collections::{BTreeMap, HashSet}; @@ -96,62 +97,29 @@ impl<'a> Path<'a> { } } -#[aoc(day8, part1)] -fn part1(input: &str) -> u64 { - parse(input).traverse() +pub fn part_one(input: &str) -> Option { + Some(parse(input).traverse()) } -#[aoc(day8, part2)] -fn part2(input: &str) -> u64 { - parse(input).ghost_traverse() +pub fn part_two(input: &str) -> Option { + Some(parse(input).ghost_traverse()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA1: &'static str = r#"RL - -AAA = (BBB, CCC) -BBB = (DDD, EEE) -CCC = (ZZZ, GGG) -DDD = (DDD, DDD) -EEE = (EEE, EEE) -GGG = (GGG, GGG) -ZZZ = (ZZZ, ZZZ) -"#; - #[test] - fn sample_data1() { - assert_eq!(part1(&SAMPLE_DATA1), 2); + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(2)); } - const SAMPLE_DATA2: &'static str = r#"LLR - -AAA = (BBB, BBB) -BBB = (AAA, ZZZ) -ZZZ = (ZZZ, ZZZ) -"#; - #[test] - fn sample_data2() { - assert_eq!(part1(&SAMPLE_DATA2), 6); - } - - const SAMPLE_DATA_P2: &'static str = r#"LR - -11A = (11B, XXX) -11B = (XXX, 11Z) -11Z = (11B, XXX) -22A = (22B, XXX) -22B = (22C, 22C) -22C = (22Z, 22Z) -22Z = (22B, 22B) -XXX = (XXX, XXX) -"#; - - #[test] - fn sample_data_part2() { - assert_eq!(part2(&SAMPLE_DATA_P2), 6); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file_part( + "examples", DAY, 2, + )); + assert_eq!(result, Some(6)); } } diff --git a/2023/src/day09.rs b/2023/src/bin/09.rs similarity index 51% rename from 2023/src/day09.rs rename to 2023/src/bin/09.rs index 4164459..f470b53 100644 --- a/2023/src/day09.rs +++ b/2023/src/bin/09.rs @@ -1,18 +1,17 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(9); -type Seq = Vec; +type Seq = Vec; fn all_zero(s: &Seq) -> bool { s.iter().find(|t| **t != 0).is_none() } -#[aoc_generator(day9)] fn parse(input: &str) -> Vec { input .lines() .filter_map(|line| { if !line.is_empty() { - let nums = line.split(" ").map(|n| n.parse::().unwrap()).collect(); + let nums = line.split(" ").map(|n| n.parse::().unwrap()).collect(); Some(nums) } else { None @@ -29,7 +28,7 @@ fn get_diffs(s: &Seq) -> Seq { result } -fn next_number(s: &Seq) -> i64 { +fn next_number(s: &Seq) -> i32 { if all_zero(s) { 0 } else { @@ -38,12 +37,11 @@ fn next_number(s: &Seq) -> i64 { } } -#[aoc(day9, part1)] -fn part1(input: &[Seq]) -> i64 { - input.iter().map(|s| next_number(&s)).sum() +pub fn part_one(input: &str) -> Option { + Some(parse(input).iter().map(|s| next_number(&s)).sum::().try_into().unwrap()) } -fn previous_number(s: &Seq) -> i64 { +fn previous_number(s: &Seq) -> i32 { if all_zero(s) { 0 } else { @@ -52,24 +50,23 @@ fn previous_number(s: &Seq) -> i64 { } } -#[aoc(day9, part2)] -fn part2(input: &[Seq]) -> i64 { - input.iter().map(|s| previous_number(&s)).sum() +pub fn part_two(input: &str) -> Option { + Some(parse(input).iter().map(|s| previous_number(&s)).sum::().try_into().unwrap()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"0 3 6 9 12 15 -1 3 6 10 15 21 -10 13 16 21 30 45 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(114)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 114); - assert_eq!(part2(&input), 2); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(2)); } } diff --git a/2023/src/day10.rs b/2023/src/bin/10.rs similarity index 82% rename from 2023/src/day10.rs rename to 2023/src/bin/10.rs index b046538..68b56f0 100644 --- a/2023/src/day10.rs +++ b/2023/src/bin/10.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(10); + use std::collections::HashSet; use std::str::FromStr; @@ -41,7 +42,6 @@ impl FromStr for Pieces { } } -#[aoc_generator(day10)] fn parse(input: &str) -> MetalIsland { let mut m: Map = [[Pieces::Empty; 143]; 143]; let mut row: usize = 1; @@ -147,8 +147,8 @@ fn next_step(path: &Vec, map: &Map) -> Step { } } -#[aoc(day10, part1)] -fn part1(input: &MetalIsland) -> u64 { +pub fn part_one(input_str: &str) -> Option { + let input = parse(input_str); let next_steps = start_steps(&input.start, &input.map); let mut left_path: Vec = vec![]; left_path.push(input.start); @@ -168,11 +168,11 @@ fn part1(input: &MetalIsland) -> u64 { } assert_eq!(left_path.len(), right_path.len()); - left_path.len() as u64 - 1 + Some(left_path.len() as u32 - 1) } -#[aoc(day10, part2)] -fn part2(input: &MetalIsland) -> u64 { +pub fn part_two(input_str: &str) -> Option { + let input = parse(input_str); let next_steps = start_steps(&input.start, &input.map); let mut path: Vec = vec![]; path.push(input.start); @@ -208,71 +208,40 @@ fn part2(input: &MetalIsland) -> u64 { // two and not 1. I can't figure out the one-square-duplication. count -= 1; } - count + Some(count) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"..... -.S-7. -.|.|. -.L-J. -..... -"#; - #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 4); + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(4)); } - const SAMPLE_DATA2: &'static str = r#"..F7. -.FJ|. -SJ.L7 -|F--J -LJ... -"#; - #[test] - fn sample_data2() { - let input = parse(&SAMPLE_DATA2); - assert_eq!(part1(&input), 8); + fn test_part_two() { + let result = part_one(&advent_of_code::template::read_file_part( + "examples", DAY, 2, + )); + assert_eq!(result, Some(8)); } - const SAMPLE_DATA3: &'static str = r#".......... -.S------7. -.|F----7|. -.||....||. -.||....||. -.|L-7F-J|. -.|..||..|. -.L--JL--J. -.......... -"#; - #[test] - fn sample_data3() { - let input = parse(&SAMPLE_DATA3); - assert_eq!(part2(&input), 4); + fn test_part_three() { + let result = part_two(&advent_of_code::template::read_file_part( + "examples", DAY, 3, + )); + assert_eq!(result, Some(4)); } - const SAMPLE_DATA4: &'static str = r#".F----7F7F7F7F-7.... -.|F--7||||||||FJ.... -.||.FJ||||||||L7.... -FJL7L7LJLJ||LJ.L-7.. -L--J.L7...LJS7F-7L7. -....F-J..F7FJ|L7L7L7 -....L7.F7||L7|.L7L7| -.....|FJLJ|FJ|F7|.LJ -....FJL-7.||.||||... -....L---J.LJ.LJLJ... -"#; - #[test] - fn sample_data4() { - let input = parse(&SAMPLE_DATA4); - assert_eq!(part2(&input), 8); + fn test_part_four() { + let result = part_two(&advent_of_code::template::read_file_part( + "examples", DAY, 4, + )); + assert_eq!(result, Some(8)); } } diff --git a/2023/src/day11.rs b/2023/src/bin/11.rs similarity index 82% rename from 2023/src/day11.rs rename to 2023/src/bin/11.rs index bf23d9b..dfb56ac 100644 --- a/2023/src/day11.rs +++ b/2023/src/bin/11.rs @@ -1,4 +1,4 @@ -use aoc_runner_derive::aoc; +advent_of_code::solution!(11); struct Galaxy(Vec); @@ -18,12 +18,12 @@ impl Galaxy { result } - fn sum_shortest_paths(&self) -> usize { + fn sum_shortest_paths(&self) -> u32 { let mut result: usize = 0; for (src, dest) in self.galaxy_pairs().iter() { result += src.path_len(&dest); } - result + result as u32 } } @@ -123,35 +123,22 @@ fn parse(input: &str, empty_space: usize) -> Galaxy { Galaxy(galaxies) } -#[aoc(day11, part1)] -fn part1(input: &str) -> usize { - parse(&input, 1).sum_shortest_paths() +pub fn part_one(input: &str) -> Option { + Some(parse(&input, 1).sum_shortest_paths()) } -#[aoc(day11, part2)] -fn part2(input: &str) -> usize { - parse(&input, 999999).sum_shortest_paths() +pub fn part_two(input: &str) -> Option { + Some(parse(&input, 999999).sum_shortest_paths()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"...#...... -.......#.. -#......... -.......... -......#... -.#........ -.........# -.......... -.......#.. -#...#..... -"#; - #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA, 1); + fn test_part_one() { + let input_str = advent_of_code::template::read_file("examples", DAY); + let input = parse(&input_str, 1); assert_eq!(input.0.len(), 9); assert_eq!(input.galaxy_pairs().len(), 36); assert_eq!(input.0[4].path_len(&input.0[8]), 9); @@ -159,7 +146,7 @@ mod tests { assert_eq!(input.0[2].path_len(&input.0[5]), 17); assert_eq!(input.0[7].path_len(&input.0[8]), 5); assert_eq!(input.sum_shortest_paths(), 374); - assert_eq!(part1(&SAMPLE_DATA), 374); + assert_eq!(part_one(&input_str), Some(374)); } #[test] @@ -170,8 +157,14 @@ mod tests { } #[test] - fn part2() { - assert_eq!(parse(&SAMPLE_DATA, 9).sum_shortest_paths(), 1030); - assert_eq!(parse(&SAMPLE_DATA, 99).sum_shortest_paths(), 8410); + fn test_part_two() { + assert_eq!( + parse(&advent_of_code::template::read_file("examples", DAY), 9).sum_shortest_paths(), + 1030 + ); + assert_eq!( + parse(&advent_of_code::template::read_file("examples", DAY), 99).sum_shortest_paths(), + 8410 + ); } } diff --git a/2023/src/day12.rs b/2023/src/bin/12.rs similarity index 86% rename from 2023/src/day12.rs rename to 2023/src/bin/12.rs index 0276a2f..5d8a480 100644 --- a/2023/src/day12.rs +++ b/2023/src/bin/12.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(12); + use std::collections::HashMap; use std::str::FromStr; @@ -29,7 +30,6 @@ struct Spring { groups: Vec, } -#[aoc_generator(day12)] fn parse(input: &str) -> Vec { input .lines() @@ -114,18 +114,20 @@ fn calc_damaged_solutions(list: &Vec, groups: &Vec, cache: &mu result } -#[aoc(day12, part1)] -fn part1(input: &[Spring]) -> usize { +pub fn part_one(input: &str) -> Option { let mut result: usize = 0; - for src in input.iter() { + for src in parse(input).iter() { let mut cache: Cache = HashMap::new(); result += calc_solutions(&src.list, &src.groups, &mut cache); } - result + Some(result as u32) } -#[aoc(day12, part2)] -fn part2(input: &[Spring]) -> usize { +pub fn part_two(input: &str) -> Option { + part2(&parse(input)) +} + +fn part2(input: &[Spring]) -> Option { let mut result: usize = 0; for src in input.iter() { let mut l: Vec = Vec::with_capacity(src.groups.len() * 5 + 5); @@ -141,25 +143,23 @@ fn part2(input: &[Spring]) -> usize { let s = calc_solutions(&l, &g, &mut cache); result += s; } - result + Some(result.try_into().unwrap()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"???.### 1,1,3 -.??..??...?##. 1,1,3 -?#?#?#?#?#?#?#? 1,3,1,6 -????.#...#... 4,1,1 -????.######..#####. 1,6,5 -?###???????? 3,2,1 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(21)); + } #[test] - fn sample_data_part1() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 21); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(525152)); } #[test] @@ -176,7 +176,7 @@ mod tests { ], groups: vec![1, 1, 3], }; - assert_eq!(part2(&[line1]), 1); + assert_eq!(part2(&[line1]), Some(1)); let line2 = Spring { list: vec![ @@ -197,12 +197,6 @@ mod tests { ], groups: vec![1, 1, 3], }; - assert_eq!(part2(&[line2]), 16384); - } - - #[test] - fn sample_data_part2() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part2(&input), 525152); + assert_eq!(part2(&[line2]), Some(16384)); } } diff --git a/2023/src/day13.rs b/2023/src/bin/13.rs similarity index 81% rename from 2023/src/day13.rs rename to 2023/src/bin/13.rs index e2bf663..b96dc9a 100644 --- a/2023/src/day13.rs +++ b/2023/src/bin/13.rs @@ -1,4 +1,4 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(13); type Board = Vec>; type Pair = (usize, usize); @@ -85,20 +85,20 @@ impl Map { None } - fn part1(&self) -> usize { + fn part1(&self) -> u32 { if self.width < self.height { if let Some(n) = self.get_horiz_score() { - return n; + return n as u32; } if let Some(n) = self.get_vert_score() { - return n; + return n as u32; } } else { if let Some(n) = self.get_vert_score() { - return n; + return n as u32; } if let Some(n) = self.get_horiz_score() { - return n; + return n as u32; } } 0 @@ -165,16 +165,16 @@ impl Map { None } - fn part2(&self) -> usize { + fn part2(&self) -> u32 { let hscore = self.get_horiz_smudge(); let vscore = self.get_vert_smudge(); if hscore.is_some() && vscore.is_some() { panic!("got both scores!"); } if hscore.is_some() { - hscore.unwrap() + hscore.unwrap() as u32 } else if vscore.is_some() { - vscore.unwrap() + vscore.unwrap() as u32 } else { 0 } @@ -200,7 +200,6 @@ fn gen_map(cur: &Board) -> Map { } // don't forget to add an extra empty newline -#[aoc_generator(day13)] fn parse(input: &str) -> Vec { let mut result: Vec = vec![]; let mut cur: Board = vec![]; @@ -224,91 +223,36 @@ fn parse(input: &str) -> Vec { result } -#[aoc(day13, part1)] -fn part1(input: &[Map]) -> usize { - input.iter().map(|t| t.part1()).sum() +pub fn part_one(input: &str) -> Option { + Some(parse(input).iter().map(|t| t.part1()).sum()) } -#[aoc(day13, part2)] -fn part2(input: &[Map]) -> usize { - input.iter().map(|t| t.part2()).sum() +pub fn part_two(input: &str) -> Option { + Some(parse(input).iter().map(|t| t.part2()).sum()) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"#.##..##. -..#.##.#. -##......# -##......# -..#.##.#. -..##..##. -#.#.##.#. - -#...##..# -#....#..# -..##..### -#####.##. -#####.##. -..##..### -#....#..# - -"#; - #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 405); - assert_eq!(part2(&input), 400); + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(405)); } - const SAMPLE_DATA2: &'static str = r#"#.##..##. -..#.##.#. -##......# -##......# -..#.##.#. -..##..##. -#.#.##.#. - -#...##..# -#....#..# -..##..### -#####.##. -#####.##. -..##..### -#....#..# - -.#.##.#.# -.##..##.. -.#.##.#.. -#......## -#......## -.#.##.#.. -.##..##.# - -#..#....# -###..##.. -.##.##### -.##.##### -###..##.. -#..#....# -#..##...# - -#.##..##. -..#.##.#. -##..#...# -##...#..# -..#.##.#. -..##..##. -#.#.##.#. - -"#; + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(400)); + } #[test] fn sample_data2() { - let input = parse(&SAMPLE_DATA2); - assert_eq!(part1(&input), 709); + let result = part_one(&advent_of_code::template::read_file_part( + "examples", DAY, 2, + )); + assert_eq!(result, Some(709)); // assert_eq!(part2(&input), 1400); } } diff --git a/2023/src/day14.rs b/2023/src/bin/14.rs similarity index 87% rename from 2023/src/day14.rs rename to 2023/src/bin/14.rs index a81c536..0e1f55f 100644 --- a/2023/src/day14.rs +++ b/2023/src/bin/14.rs @@ -1,7 +1,7 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(14); + use std::collections::HashSet; -#[aoc_generator(day14)] fn parse(input: &str) -> Vec { input .lines() @@ -33,7 +33,7 @@ fn map_to_string(map: &[[char; 100]; 100]) -> String { s } -fn score(input: &str) -> usize { +fn score(input: &str) -> u32 { let lines = input .split("\n") .filter_map(|t| { @@ -49,19 +49,18 @@ fn score(input: &str) -> usize { for (index, line) in lines.iter().enumerate() { score += line.matches("O").collect::>().len() * (total_lines - index); } - score + score.try_into().unwrap() } -#[aoc(day14, part1)] -fn part1(input: &[String]) -> usize { +pub fn part_one(input: &str) -> Option { let mut map: [[char; 100]; 100] = [[' '; 100]; 100]; - for (row, line) in input.iter().enumerate() { + for (row, line) in parse(input).iter().enumerate() { for (col, c) in line.chars().enumerate() { map[row][col] = c; } } tilt_north(&mut map); - score(&map_to_string(&map)) + Some(score(&map_to_string(&map))) } fn tilt_north(map: &mut [[char; 100]; 100]) -> String { @@ -160,10 +159,9 @@ fn tilt_east(map: &mut [[char; 100]; 100]) -> String { map_to_string(&map) } -#[aoc(day14, part2)] -fn part2(input: &[String]) -> usize { +pub fn part_two(input: &str) -> Option { let mut map: [[char; 100]; 100] = [[' '; 100]; 100]; - for (row, line) in input.iter().enumerate() { + for (row, line) in parse(input).iter().enumerate() { for (col, c) in line.chars().enumerate() { map[row][col] = c; } @@ -186,29 +184,22 @@ fn part2(input: &[String]) -> usize { } let score = score(&seq[999]); - score + Some(score) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"O....#.... -O.OO#....# -.....##... -OO.#O....O -.O.....O#. -O.#..O.#.# -..O..#O..O -.......O.. -#....###.. -#OO..#.... -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(136)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 136); - assert_eq!(part2(&input), 64); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(64)); } } diff --git a/2023/src/day15.rs b/2023/src/bin/15.rs similarity index 70% rename from 2023/src/day15.rs rename to 2023/src/bin/15.rs index a4be8e2..1b68864 100644 --- a/2023/src/day15.rs +++ b/2023/src/bin/15.rs @@ -1,20 +1,18 @@ -use aoc_runner_derive::aoc; +advent_of_code::solution!(15); fn hash(input: &str) -> usize { let mut cur: usize = 0; for c in input.chars() { cur = ((cur + c as usize) * 17) % 256; } - cur + cur.try_into().unwrap() } -#[aoc(day15, part1)] -fn part1(input: &str) -> usize { - input.trim_end().split(",").map(|t| hash(t)).sum() +pub fn part_one(input: &str) -> Option { + Some(input.trim_end().split(",").map(|t| hash(t)).sum()) } -#[aoc(day15, part2)] -fn part2(input: &str) -> usize { +pub fn part_two(input: &str) -> Option { let mut boxes: Vec> = vec![]; for _ in 0..256 { boxes.push(vec![]); @@ -47,18 +45,22 @@ fn part2(input: &str) -> usize { sum += (box_index + 1) * (index + 1) * lens.1; } } - sum + Some(sum) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = "rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7"; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(1320)); + } #[test] - fn sample_data() { - assert_eq!(part1(&SAMPLE_DATA), 1320); - assert_eq!(part2(&SAMPLE_DATA), 145); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(145)); } } diff --git a/2023/src/day16.rs b/2023/src/bin/16.rs similarity index 85% rename from 2023/src/day16.rs rename to 2023/src/bin/16.rs index 9850c99..d96ac9a 100644 --- a/2023/src/day16.rs +++ b/2023/src/bin/16.rs @@ -1,7 +1,7 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(16); + use std::collections::HashSet; -#[aoc_generator(day16)] fn parse(input: &str) -> Vec> { input .lines() @@ -134,22 +134,22 @@ fn energized_squares(visited: &HashSet<(i64, i64, Dir)>) -> usize { t.len() } -#[aoc(day16, part1)] -fn part1(input: &[Vec]) -> usize { +pub fn part_one(input_str: &str) -> Option { + let input = parse(input_str); let mut visited: HashSet<(i64, i64, Dir)> = HashSet::new(); - visited_spaces(input, &mut visited, 0, 0, Dir::East, 0); - energized_squares(&visited) + visited_spaces(&input, &mut visited, 0, 0, Dir::East, 0); + Some(energized_squares(&visited)) } -#[aoc(day16, part2)] -fn part2(input: &[Vec]) -> usize { +pub fn part_two(input_str: &str) -> Option { + let input = parse(input_str); let mut max = 0; let xlen = input.len() - 1; let ylen = input[0].len() - 1; for t in 0..=xlen { let mut evisited: HashSet<(i64, i64, Dir)> = HashSet::new(); - visited_spaces(input, &mut evisited, t as i64, 0, Dir::East, 0); + visited_spaces(&input, &mut evisited, t as i64, 0, Dir::East, 0); let emax = energized_squares(&evisited); if emax > max { max = emax; @@ -157,7 +157,7 @@ fn part2(input: &[Vec]) -> usize { let mut wvisited: HashSet<(i64, i64, Dir)> = HashSet::new(); visited_spaces( - input, + &input, &mut wvisited, xlen as i64, (ylen - t) as i64, @@ -172,7 +172,7 @@ fn part2(input: &[Vec]) -> usize { for t in 0..=ylen { let mut svisited: HashSet<(i64, i64, Dir)> = HashSet::new(); - visited_spaces(input, &mut svisited, 0, t as i64, Dir::South, 0); + visited_spaces(&input, &mut svisited, 0, t as i64, Dir::South, 0); let smax = energized_squares(&svisited); if smax > max { max = smax; @@ -180,7 +180,7 @@ fn part2(input: &[Vec]) -> usize { let mut nvisited: HashSet<(i64, i64, Dir)> = HashSet::new(); visited_spaces( - input, + &input, &mut nvisited, xlen as i64, (ylen - t) as i64, @@ -193,28 +193,22 @@ fn part2(input: &[Vec]) -> usize { } } - max + Some(max) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#".|...\.... -|.-.\..... -.....|-... -........|. -.......... -.........\ -..../.\\.. -.-.-/..|.. -.|....-|.\ -..//.|...."#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(46)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 46); - assert_eq!(part2(&input), 51); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(51)); } } diff --git a/2023/src/day17.rs b/2023/src/bin/17.rs similarity index 83% rename from 2023/src/day17.rs rename to 2023/src/bin/17.rs index ffdf924..ac73f84 100644 --- a/2023/src/day17.rs +++ b/2023/src/bin/17.rs @@ -1,8 +1,8 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(17); + use pathfinding::matrix::{directions, Matrix}; use pathfinding::prelude::astar; -#[aoc_generator(day17)] fn parse(input: &str) -> Matrix { input .lines() @@ -98,38 +98,27 @@ fn continue_in_direction(map: &Matrix, p: &Path) -> Vec } } -#[aoc(day17, part1)] -fn part1(input: &Matrix) -> usize { - find_path::<1, 3>(&input) +pub fn part_one(input: &str) -> Option { + Some(find_path::<1, 3>(&parse(input))) } -#[aoc(day17, part2)] -fn part2(input: &Matrix) -> usize { - find_path::<4, 10>(&input) +pub fn part_two(input: &str) -> Option { + Some(find_path::<4, 10>(&parse(input))) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"2413432311323 -3215453535623 -3255245654254 -3446585845452 -4546657867536 -1438598798454 -4457876987766 -3637877979653 -4654967986887 -4564679986453 -1224686865563 -2546548887735 -4322674655533 -"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(102)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 102); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(94)); } } diff --git a/2023/src/day18.rs b/2023/src/bin/18.rs similarity index 77% rename from 2023/src/day18.rs rename to 2023/src/bin/18.rs index 0ff6898..b52f5a8 100644 --- a/2023/src/day18.rs +++ b/2023/src/bin/18.rs @@ -1,5 +1,6 @@ +advent_of_code::solution!(18); + use aoc_parse::{parser, prelude::*}; -use aoc_runner_derive::{aoc, aoc_generator}; use std::ops::{Add, Div}; #[derive(Clone, Copy)] @@ -16,7 +17,6 @@ struct Move { color: String, } -#[aoc_generator(day18)] fn parse(input: &str) -> Vec { let p = parser!(lines({ "U" => Dir::N, "R" => Dir::E, "D" => Dir::S, "L" => Dir::W } " " usize " (#" string(digit_hex+) ")")); p.parse(input) @@ -66,14 +66,12 @@ fn shoelace(coords: &[(isize, isize)]) -> isize { area.abs().add(perimeter).div(2).add(1) } -#[aoc(day18, part1)] -fn part1(input: &[Move]) -> isize { - shoelace(&get_perimeter(input)) +pub fn part_one(input: &str) -> Option { + Some(shoelace(&get_perimeter(&parse(input)))) } -#[aoc(day18, part2)] -fn part2(input: &[Move]) -> isize { - let moves = input +pub fn part_two(input: &str) -> Option { + let moves = parse(input) .iter() .map(|m| { let mut s = m.color.clone(); @@ -92,32 +90,22 @@ fn part2(input: &[Move]) -> isize { } }) .collect::>(); - shoelace(&get_perimeter(&moves)) + Some(shoelace(&get_perimeter(&moves))) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"R 6 (#70c710) -D 5 (#0dc571) -L 2 (#5713f0) -D 2 (#d2c081) -R 2 (#59c680) -D 2 (#411b91) -L 5 (#8ceee2) -U 2 (#caa173) -L 1 (#1b58a2) -U 2 (#caa171) -R 2 (#7807d2) -U 3 (#a77fa3) -L 2 (#015232) -U 2 (#7a21e3)"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(62)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 62); - assert_eq!(part2(&input), 952408144115); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(952408144115)); } } diff --git a/2023/src/day19.rs b/2023/src/bin/19.rs similarity index 88% rename from 2023/src/day19.rs rename to 2023/src/bin/19.rs index 0c01b2e..5b67319 100644 --- a/2023/src/day19.rs +++ b/2023/src/bin/19.rs @@ -1,5 +1,6 @@ +advent_of_code::solution!(19); + use aoc_parse::{parser, prelude::*}; -use aoc_runner_derive::aoc; use std::collections::HashMap; struct Part { @@ -138,12 +139,12 @@ fn filter_part(rules: &Rules, part: &Part) -> u64 { 0 } -#[aoc(day19, part1)] -fn part1(input: &str) -> u64 { +pub fn part_one(input: &str) -> Option { let (rules, parts) = parse(input); - parts.iter().map(|p| filter_part(&rules, &p)).sum() + Some(parts.iter().map(|p| filter_part(&rules, &p)).sum()) } + fn combinations(weights: [(u64, u64); 4]) -> u64 { (0..4) .into_iter() @@ -215,37 +216,24 @@ fn traverse_rules(rules: &Rules, cur: &str, mut weights: [(u64, u64); 4]) -> u64 c } -#[aoc(day19, part2)] -fn part2(input: &str) -> u64 { +pub fn part_two(input: &str) -> Option { let (rules, _parts) = parse(input); - traverse_rules(&rules, "in", [(1, 4001); 4]) + Some(traverse_rules(&rules, "in", [(1, 4001); 4])) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"px{a<2006:qkq,m>2090:A,rfg} -pv{a>1716:R,A} -lnx{m>1548:A,A} -rfg{s<537:gd,x>2440:R,A} -qs{s>3448:A,lnx} -qkq{x<1416:A,crn} -crn{x>2662:A,R} -in{s<1351:px,qqz} -qqz{s>2770:qs,m<1801:hdj,R} -gd{a>3333:R,R} -hdj{m>838:A,pv} - -{x=787,m=2655,a=1222,s=2876} -{x=1679,m=44,a=2067,s=496} -{x=2036,m=264,a=79,s=2244} -{x=2461,m=1339,a=466,s=291} -{x=2127,m=1623,a=2188,s=1013}"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(19114)); + } #[test] - fn sample_data() { - assert_eq!(part1(&SAMPLE_DATA), 19114); - assert_eq!(part2(&SAMPLE_DATA), 167409079868000); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(167409079868000)); } } diff --git a/2023/src/day20.rs b/2023/src/bin/20.rs similarity index 89% rename from 2023/src/day20.rs rename to 2023/src/bin/20.rs index a8511e1..40661eb 100644 --- a/2023/src/day20.rs +++ b/2023/src/bin/20.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::aoc; +advent_of_code::solution!(20); + use num::integer::lcm; use std::collections::{HashMap, VecDeque}; @@ -139,8 +140,7 @@ fn parse<'a>(input: &str) -> HashMap { r } -#[aoc(day20, part1)] -fn part1(input: &str) -> u64 { +pub fn part_one(input: &str) -> Option { let mut parts = parse(input); let mut queue: VecDeque<(String, String, bool)> = VecDeque::new(); let mut high = 0; @@ -159,11 +159,10 @@ fn part1(input: &str) -> u64 { } } - high * low + Some(high * low) } -#[aoc(day20, part2)] -fn part2(input: &str) -> u64 { +pub fn part_two(input: &str) -> Option { let mut parts = parse(input); let mut queue: VecDeque<(String, String, bool)> = VecDeque::new(); let mut last_cons = vec![ @@ -183,7 +182,7 @@ fn part2(input: &str) -> u64 { con_indexes.push(i + 1); c = lcm(i + 1, c); if last_cons.is_empty() { - return c; + return Some(c); } } } @@ -192,34 +191,30 @@ fn part2(input: &str) -> u64 { } } } - 0 + None } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"broadcaster -> a, b, c -%a -> b -%b -> c -%c -> inv -&inv -> a -"#; - - const SAMPLE_DATA2: &'static str = r#"broadcaster -> a -%a -> inv, con -&inv -> b -%b -> con -&con -> output"#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(32000000)); + } #[test] - fn sample_data() { - assert_eq!(part1(&SAMPLE_DATA), 32000000); - // assert_eq!(part2(&SAMPLE_DATA), 71503); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); } #[test] fn sample_data2() { - assert_eq!(part1(&SAMPLE_DATA2), 11687500); + let result = part_one(&advent_of_code::template::read_file_part( + "examples", DAY, 2, + )); + assert_eq!(result, Some(11687500)); } } diff --git a/2023/src/day21.rs b/2023/src/bin/21.rs similarity index 79% rename from 2023/src/day21.rs rename to 2023/src/bin/21.rs index c2b07c4..b876b9c 100644 --- a/2023/src/day21.rs +++ b/2023/src/bin/21.rs @@ -1,4 +1,5 @@ -use aoc_runner_derive::{aoc, aoc_generator}; +advent_of_code::solution!(21); + use std::collections::{HashMap, VecDeque}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -6,7 +7,6 @@ struct Point(usize, usize); type Map = [[char; 131]; 131]; -#[aoc_generator(day21)] fn parse(input: &str) -> (Point, Map) { let mut m = [['#'; 131]; 131]; let mut start: Point = Point(0, 0); @@ -73,17 +73,18 @@ fn infinite_step(start: &Point, map: &Map) -> HashMap { visited } -#[aoc(day21, part1)] -fn part1(input: &(Point, Map)) -> usize { +pub fn part_one(input_str: &str) -> Option { + let input = parse(input_str); let visited = infinite_step(&input.0, &input.1); - visited + let r = visited .values() .filter(|t| **t <= 64 && **t % 2 == 0) - .count() + .count(); + Some(r) } -#[aoc(day21, part2)] -fn part2(input: &(Point, Map)) -> usize { +pub fn part_two(input_str: &str) -> Option { + let input = parse(input_str); let visited = infinite_step(&input.0, &input.1); let even_corners = visited @@ -101,28 +102,24 @@ fn part2(input: &(Point, Map)) -> usize { let n = ((26501365 - (input.1.len() / 2)) / input.1.len()) as usize; assert_eq!(n, 202300); - ((n + 1) * (n + 1)) * odd_full + (n * n) * even_full - (n + 1) * odd_corners + n * even_corners + let r = ((n + 1) * (n + 1)) * odd_full + (n * n) * even_full - (n + 1) * odd_corners + + n * even_corners; + Some(r) } #[cfg(test)] mod tests { use super::*; - const SAMPLE_DATA: &'static str = r#"........... -.....###.#. -.###.##..#. -..#.#...#.. -....#.#.... -.##..S####. -.##..#...#. -.......##.. -.##.#.####. -.##..##.##. -..........."#; + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(42)); + } #[test] - fn sample_data() { - let input = parse(&SAMPLE_DATA); - assert_eq!(part1(&input), 42); + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(3314964269439)); } } diff --git a/2023/src/bin/22.rs b/2023/src/bin/22.rs new file mode 100644 index 0000000..ebaa693 --- /dev/null +++ b/2023/src/bin/22.rs @@ -0,0 +1,26 @@ +advent_of_code::solution!(22); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/2023/src/bin/23.rs b/2023/src/bin/23.rs new file mode 100644 index 0000000..6c6a946 --- /dev/null +++ b/2023/src/bin/23.rs @@ -0,0 +1,26 @@ +advent_of_code::solution!(23); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/2023/src/bin/24.rs b/2023/src/bin/24.rs new file mode 100644 index 0000000..c4d88f4 --- /dev/null +++ b/2023/src/bin/24.rs @@ -0,0 +1,26 @@ +advent_of_code::solution!(24); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/2023/src/bin/25.rs b/2023/src/bin/25.rs new file mode 100644 index 0000000..dbbfd18 --- /dev/null +++ b/2023/src/bin/25.rs @@ -0,0 +1,26 @@ +advent_of_code::solution!(25); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index 492c1a0..27d7df8 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -1,25 +1,3 @@ -use aoc_runner_derive::aoc_lib; +pub mod template; -mod day01; -mod day02; -mod day03; -mod day04; -mod day05; -mod day06; -mod day07; -mod day08; -mod day09; -mod day10; -mod day11; -mod day12; -mod day13; -mod day14; -mod day15; -mod day16; -mod day17; -mod day18; -mod day19; -mod day20; -mod day21; - -aoc_lib! { year = 2023 } +// Use this file to add helper functions and additional modules. diff --git a/2023/src/main.rs b/2023/src/main.rs index 5ae2887..9322423 100644 --- a/2023/src/main.rs +++ b/2023/src/main.rs @@ -1,7 +1,138 @@ -extern crate aoc2023; -extern crate aoc_runner; -extern crate aoc_runner_derive; +use advent_of_code::template::commands::{all, download, read, scaffold, solve, time}; +use args::{parse, AppArguments}; -use aoc_runner_derive::aoc_main; +#[cfg(feature = "today")] +use advent_of_code::template::Day; +#[cfg(feature = "today")] +use std::process; -aoc_main! { lib = aoc2023; } +mod args { + use advent_of_code::template::Day; + use std::process; + + pub enum AppArguments { + Download { + day: Day, + }, + Read { + day: Day, + }, + Scaffold { + day: Day, + download: bool, + }, + Solve { + day: Day, + release: bool, + dhat: bool, + submit: Option, + }, + All { + release: bool, + }, + Time { + all: bool, + day: Option, + store: bool, + }, + #[cfg(feature = "today")] + Today, + } + + pub fn parse() -> Result> { + let mut args = pico_args::Arguments::from_env(); + + let app_args = match args.subcommand()?.as_deref() { + Some("all") => AppArguments::All { + release: args.contains("--release"), + }, + Some("time") => { + let all = args.contains("--all"); + let store = args.contains("--store"); + + AppArguments::Time { + all, + day: args.opt_free_from_str()?, + store, + } + } + Some("download") => AppArguments::Download { + day: args.free_from_str()?, + }, + Some("read") => AppArguments::Read { + day: args.free_from_str()?, + }, + Some("scaffold") => AppArguments::Scaffold { + day: args.free_from_str()?, + download: args.contains("--download"), + }, + Some("solve") => AppArguments::Solve { + day: args.free_from_str()?, + release: args.contains("--release"), + submit: args.opt_value_from_str("--submit")?, + dhat: args.contains("--dhat"), + }, + #[cfg(feature = "today")] + Some("today") => AppArguments::Today, + Some(x) => { + eprintln!("Unknown command: {x}"); + process::exit(1); + } + None => { + eprintln!("No command specified."); + process::exit(1); + } + }; + + let remaining = args.finish(); + if !remaining.is_empty() { + eprintln!("Warning: unknown argument(s): {remaining:?}."); + } + + Ok(app_args) + } +} + +fn main() { + match parse() { + Err(err) => { + eprintln!("Error: {err}"); + std::process::exit(1); + } + Ok(args) => match args { + AppArguments::All { release } => all::handle(release), + AppArguments::Time { day, all, store } => time::handle(day, all, store), + AppArguments::Download { day } => download::handle(day), + AppArguments::Read { day } => read::handle(day), + AppArguments::Scaffold { day, download } => { + scaffold::handle(day); + if download { + download::handle(day); + } + } + AppArguments::Solve { + day, + release, + dhat, + submit, + } => solve::handle(day, release, dhat, submit), + #[cfg(feature = "today")] + AppArguments::Today => { + match Day::today() { + Some(day) => { + scaffold::handle(day); + download::handle(day); + read::handle(day) + } + None => { + eprintln!( + "`today` command can only be run between the 1st and \ + the 25th of december. Please use `scaffold` with a specific day." + ); + process::exit(1) + } + }; + } + }, + }; +} diff --git a/2023/src/template.txt b/2023/src/template.txt new file mode 100644 index 0000000..11344df --- /dev/null +++ b/2023/src/template.txt @@ -0,0 +1,26 @@ +advent_of_code::solution!(%DAY_NUMBER%); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +} diff --git a/2023/src/template/aoc_cli.rs b/2023/src/template/aoc_cli.rs new file mode 100644 index 0000000..2d3300d --- /dev/null +++ b/2023/src/template/aoc_cli.rs @@ -0,0 +1,125 @@ +/// Wrapper module around the "aoc-cli" command-line. +use std::{ + fmt::Display, + process::{Command, Output, Stdio}, +}; + +use crate::template::Day; + +#[derive(Debug)] +pub enum AocCommandError { + CommandNotFound, + CommandNotCallable, + BadExitStatus(Output), +} + +impl Display for AocCommandError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AocCommandError::CommandNotFound => write!(f, "aoc-cli is not present in environment."), + AocCommandError::CommandNotCallable => write!(f, "aoc-cli could not be called."), + AocCommandError::BadExitStatus(_) => { + write!(f, "aoc-cli exited with a non-zero status.") + } + } + } +} + +pub fn check() -> Result<(), AocCommandError> { + Command::new("aoc") + .arg("-V") + .output() + .map_err(|_| AocCommandError::CommandNotFound)?; + Ok(()) +} + +pub fn read(day: Day) -> Result { + let puzzle_path = get_puzzle_path(day); + + let args = build_args( + "read", + &[ + "--description-only".into(), + "--puzzle-file".into(), + puzzle_path, + ], + day, + ); + + call_aoc_cli(&args) +} + +pub fn download(day: Day) -> Result { + 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)?; + println!("---"); + println!("๐ŸŽ„ Successfully wrote input to \"{}\".", &input_path); + println!("๐ŸŽ„ Successfully wrote puzzle to \"{}\".", &puzzle_path); + Ok(output) +} + +pub fn submit(day: Day, part: u8, result: &str) -> Result { + // 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) +} + +fn get_input_path(day: Day) -> String { + format!("data/inputs/{day}.txt") +} + +fn get_puzzle_path(day: Day) -> String { + format!("data/puzzles/{day}.md") +} + +fn get_year() -> Option { + match std::env::var("AOC_YEAR") { + Ok(x) => x.parse().ok().or(None), + Err(_) => None, + } +} + +fn build_args(command: &str, args: &[String], day: Day) -> Vec { + let mut cmd_args = args.to_vec(); + + if let Some(year) = get_year() { + 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 { + // println!("Calling >aoc with: {}", args.join(" ")); + let output = Command::new("aoc") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .map_err(|_| AocCommandError::CommandNotCallable)?; + + if output.status.success() { + Ok(output) + } else { + Err(AocCommandError::BadExitStatus(output)) + } +} diff --git a/2023/src/template/commands/all.rs b/2023/src/template/commands/all.rs new file mode 100644 index 0000000..b844e1e --- /dev/null +++ b/2023/src/template/commands/all.rs @@ -0,0 +1,5 @@ +use crate::template::{all_days, run_multi::run_multi}; + +pub fn handle(is_release: bool) { + run_multi(&all_days().collect(), is_release, false); +} diff --git a/2023/src/template/commands/download.rs b/2023/src/template/commands/download.rs new file mode 100644 index 0000000..9274f05 --- /dev/null +++ b/2023/src/template/commands/download.rs @@ -0,0 +1,14 @@ +use crate::template::{aoc_cli, Day}; +use std::process; + +pub fn handle(day: Day) { + 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 let Err(e) = aoc_cli::download(day) { + eprintln!("failed to call aoc-cli: {e}"); + process::exit(1); + }; +} diff --git a/2023/src/template/commands/mod.rs b/2023/src/template/commands/mod.rs new file mode 100644 index 0000000..36be280 --- /dev/null +++ b/2023/src/template/commands/mod.rs @@ -0,0 +1,6 @@ +pub mod all; +pub mod download; +pub mod read; +pub mod scaffold; +pub mod solve; +pub mod time; diff --git a/2023/src/template/commands/read.rs b/2023/src/template/commands/read.rs new file mode 100644 index 0000000..3e1a307 --- /dev/null +++ b/2023/src/template/commands/read.rs @@ -0,0 +1,15 @@ +use std::process; + +use crate::template::{aoc_cli, Day}; + +pub fn handle(day: Day) { + 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 let Err(e) = aoc_cli::read(day) { + eprintln!("failed to call aoc-cli: {e}"); + process::exit(1); + }; +} diff --git a/2023/src/template/commands/scaffold.rs b/2023/src/template/commands/scaffold.rs new file mode 100644 index 0000000..4e7d7c0 --- /dev/null +++ b/2023/src/template/commands/scaffold.rs @@ -0,0 +1,69 @@ +use std::{ + fs::{File, OpenOptions}, + io::Write, + process, +}; + +use crate::template::Day; + +const MODULE_TEMPLATE: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/template.txt")); + +fn safe_create_file(path: &str) -> Result { + OpenOptions::new().write(true).create_new(true).open(path) +} + +fn create_file(path: &str) -> Result { + OpenOptions::new().write(true).create(true).open(path) +} + +pub fn handle(day: Day) { + let input_path = format!("data/inputs/{day}.txt"); + let example_path = format!("data/examples/{day}.txt"); + let module_path = format!("src/bin/{day}.rs"); + + 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_NUMBER%", &day.into_inner().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 {day}` to run your solution."); +} diff --git a/2023/src/template/commands/solve.rs b/2023/src/template/commands/solve.rs new file mode 100644 index 0000000..ec92a6f --- /dev/null +++ b/2023/src/template/commands/solve.rs @@ -0,0 +1,34 @@ +use std::process::{Command, Stdio}; + +use crate::template::Day; + +pub fn handle(day: Day, release: bool, dhat: bool, submit_part: Option) { + let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day.to_string()]; + + if dhat { + cmd_args.extend([ + "--profile".to_string(), + "dhat".to_string(), + "--features".to_string(), + "dhat-heap".to_string(), + ]); + } else if release { + cmd_args.push("--release".to_string()); + } + + cmd_args.push("--".to_string()); + + if let Some(submit_part) = submit_part { + cmd_args.push("--submit".to_string()); + cmd_args.push(submit_part.to_string()); + } + + let mut cmd = Command::new("cargo") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + cmd.wait().unwrap(); +} diff --git a/2023/src/template/commands/time.rs b/2023/src/template/commands/time.rs new file mode 100644 index 0000000..49b91a8 --- /dev/null +++ b/2023/src/template/commands/time.rs @@ -0,0 +1,40 @@ +use std::collections::HashSet; + +use crate::template::run_multi::run_multi; +use crate::template::timings::Timings; +use crate::template::{all_days, readme_benchmarks, Day}; + +pub fn handle(day: Option, run_all: bool, store: bool) { + let stored_timings = Timings::read_from_file(); + + let days_to_run = day.map_or_else( + || { + if run_all { + all_days().collect() + } else { + // when the `--all` flag is not set, filter out days that are fully benched. + all_days() + .filter(|day| !stored_timings.is_day_complete(*day)) + .collect() + } + }, + |day| HashSet::from([day]), + ); + + let timings = run_multi(&days_to_run, true, true).unwrap(); + + if store { + let merged_timings = stored_timings.merge(&timings); + merged_timings.store_file().unwrap(); + + println!(); + match readme_benchmarks::update(merged_timings) { + Ok(()) => { + println!("Stored updated benchmarks."); + } + Err(_) => { + eprintln!("Failed to store updated benchmarks."); + } + } + } +} diff --git a/2023/src/template/day.rs b/2023/src/template/day.rs new file mode 100644 index 0000000..99b8280 --- /dev/null +++ b/2023/src/template/day.rs @@ -0,0 +1,192 @@ +use std::error::Error; +use std::fmt::Display; +use std::str::FromStr; + +#[cfg(feature = "today")] +use chrono::{Datelike, FixedOffset, Utc}; + +#[cfg(feature = "today")] +const SERVER_UTC_OFFSET: i32 = -5; + +/// A valid day number of advent (i.e. an integer in range 1 to 25). +/// +/// # Display +/// This value displays as a two digit number. +/// +/// ``` +/// # use advent_of_code::Day; +/// let day = Day::new(8).unwrap(); +/// assert_eq!(day.to_string(), "08") +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Day(u8); + +impl Day { + /// Creates a [`Day`] from the provided value if it's in the valid range, + /// returns [`None`] otherwise. + pub fn new(day: u8) -> Option { + if day == 0 || day > 25 { + return None; + } + Some(Self(day)) + } + + // Not part of the public API + #[doc(hidden)] + pub const fn __new_unchecked(day: u8) -> Self { + Self(day) + } + + /// Converts the [`Day`] into an [`u8`]. + pub fn into_inner(self) -> u8 { + self.0 + } +} + +#[cfg(feature = "today")] +impl Day { + /// Returns the current day if it's between the 1st and the 25th of december, `None` otherwise. + pub fn today() -> Option { + let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600)?; + let today = Utc::now().with_timezone(&offset); + if today.month() == 12 && today.day() <= 25 { + Self::new(u8::try_from(today.day()).ok()?) + } else { + None + } + } +} + +impl Display for Day { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:02}", self.0) + } +} + +impl PartialEq for Day { + fn eq(&self, other: &u8) -> bool { + self.0.eq(other) + } +} + +impl PartialOrd for Day { + fn partial_cmp(&self, other: &u8) -> Option { + self.0.partial_cmp(other) + } +} + +/* -------------------------------------------------------------------------- */ + +impl FromStr for Day { + type Err = DayFromStrError; + + fn from_str(s: &str) -> Result { + let day = s.parse().map_err(|_| DayFromStrError)?; + Self::new(day).ok_or(DayFromStrError) + } +} + +/// An error which can be returned when parsing a [`Day`]. +#[derive(Debug)] +pub struct DayFromStrError; + +impl Error for DayFromStrError {} + +impl Display for DayFromStrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("expecting a day number between 1 and 25") + } +} + +/* -------------------------------------------------------------------------- */ + +/// An iterator that yields every day of advent from the 1st to the 25th. +pub fn all_days() -> AllDays { + AllDays::new() +} + +/// An iterator that yields every day of advent from the 1st to the 25th. +pub struct AllDays { + current: u8, +} + +impl AllDays { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { current: 1 } + } +} + +impl Iterator for AllDays { + type Item = Day; + + fn next(&mut self) -> Option { + if self.current > 25 { + return None; + } + // NOTE: the iterator starts at 1 and we have verified that the value is not above 25. + let day = Day(self.current); + self.current += 1; + + Some(day) + } +} + +/* -------------------------------------------------------------------------- */ + +/// Creates a [`Day`] value in a const context. +#[macro_export] +macro_rules! day { + ($day:expr) => {{ + const _ASSERT: () = assert!( + $day != 0 && $day <= 25, + concat!( + "invalid day number `", + $day, + "`, expecting a value between 1 and 25" + ), + ); + $crate::template::Day::__new_unchecked($day) + }}; +} + +/* -------------------------------------------------------------------------- */ + +#[cfg(feature = "test_lib")] +mod tests { + use super::{all_days, Day}; + + #[test] + fn all_days_iterator() { + let mut iter = all_days(); + + assert_eq!(iter.next(), Some(Day(1))); + assert_eq!(iter.next(), Some(Day(2))); + assert_eq!(iter.next(), Some(Day(3))); + assert_eq!(iter.next(), Some(Day(4))); + assert_eq!(iter.next(), Some(Day(5))); + assert_eq!(iter.next(), Some(Day(6))); + assert_eq!(iter.next(), Some(Day(7))); + assert_eq!(iter.next(), Some(Day(8))); + assert_eq!(iter.next(), Some(Day(9))); + assert_eq!(iter.next(), Some(Day(10))); + assert_eq!(iter.next(), Some(Day(11))); + assert_eq!(iter.next(), Some(Day(12))); + assert_eq!(iter.next(), Some(Day(13))); + assert_eq!(iter.next(), Some(Day(14))); + assert_eq!(iter.next(), Some(Day(15))); + assert_eq!(iter.next(), Some(Day(16))); + assert_eq!(iter.next(), Some(Day(17))); + assert_eq!(iter.next(), Some(Day(18))); + assert_eq!(iter.next(), Some(Day(19))); + assert_eq!(iter.next(), Some(Day(20))); + assert_eq!(iter.next(), Some(Day(21))); + assert_eq!(iter.next(), Some(Day(22))); + assert_eq!(iter.next(), Some(Day(23))); + assert_eq!(iter.next(), Some(Day(24))); + assert_eq!(iter.next(), Some(Day(25))); + assert_eq!(iter.next(), None); + } +} + +/* -------------------------------------------------------------------------- */ diff --git a/2023/src/template/mod.rs b/2023/src/template/mod.rs new file mode 100644 index 0000000..dd8e4c0 --- /dev/null +++ b/2023/src/template/mod.rs @@ -0,0 +1,68 @@ +use std::{env, fs}; + +pub mod aoc_cli; +pub mod commands; +pub mod runner; + +pub use day::*; + +mod day; +mod readme_benchmarks; +mod run_multi; +mod timings; + +pub const ANSI_ITALIC: &str = "\x1b[3m"; +pub const ANSI_BOLD: &str = "\x1b[1m"; +pub const ANSI_RESET: &str = "\x1b[0m"; + +/// Helper function that reads a text file to a string. +#[must_use] +pub fn read_file(folder: &str, day: Day) -> String { + let cwd = env::current_dir().unwrap(); + let filepath = cwd.join("data").join(folder).join(format!("{day}.txt")); + let f = fs::read_to_string(filepath); + f.expect("could not open input file") +} + +/// Helper function that reads a text file to string, appending a part suffix. E.g. like `01-2.txt`. +#[must_use] +pub fn read_file_part(folder: &str, day: Day, part: u8) -> String { + let cwd = env::current_dir().unwrap(); + let filepath = cwd + .join("data") + .join(folder) + .join(format!("{day}-{part}.txt")); + let f = fs::read_to_string(filepath); + f.expect("could not open input file") +} + +/// Creates the constant `DAY` and sets up the input and runner for each part. +/// +/// The optional, second parameter (1 or 2) allows you to only run a single part of the solution. +#[macro_export] +macro_rules! solution { + ($day:expr) => { + $crate::solution!(@impl $day, [part_one, 1] [part_two, 2]); + }; + ($day:expr, 1) => { + $crate::solution!(@impl $day, [part_one, 1]); + }; + ($day:expr, 2) => { + $crate::solution!(@impl $day, [part_two, 2]); + }; + + (@impl $day:expr, $( [$func:expr, $part:expr] )*) => { + /// The current day. + const DAY: $crate::template::Day = $crate::day!($day); + + #[cfg(feature = "dhat-heap")] + #[global_allocator] + static ALLOC: dhat::Alloc = dhat::Alloc; + + fn main() { + use $crate::template::runner::*; + let input = $crate::template::read_file("inputs", DAY); + $( run_part($func, &input, DAY, $part); )* + } + }; +} diff --git a/2023/src/template/readme_benchmarks.rs b/2023/src/template/readme_benchmarks.rs new file mode 100644 index 0000000..1498dbb --- /dev/null +++ b/2023/src/template/readme_benchmarks.rs @@ -0,0 +1,182 @@ +/// Module that updates the readme me with timing information. +/// The approach taken is similar to how `aoc-readme-stars` handles this. +use std::{fs, io}; + +use crate::template::timings::Timings; +use crate::template::Day; + +static MARKER: &str = ""; + +#[derive(Debug)] +pub enum Error { + Parser(String), + IO(io::Error), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IO(e) + } +} + +pub struct TablePosition { + pos_start: usize, + pos_end: usize, +} + +#[must_use] +pub fn get_path_for_bin(day: Day) -> String { + format!("./src/bin/{day}.rs") +} + +fn locate_table(readme: &str) -> Result { + let matches: Vec<_> = readme.match_indices(MARKER).collect(); + + if matches.len() > 2 { + return Err(Error::Parser( + "{}: too many occurences of marker in README.".into(), + )); + } + + let pos_start = matches + .first() + .map(|m| m.0) + .ok_or_else(|| Error::Parser("Could not find table start position.".into()))?; + + let pos_end = matches + .last() + .map(|m| m.0 + m.1.len()) + .ok_or_else(|| Error::Parser("Could not find table end position.".into()))?; + + Ok(TablePosition { pos_start, pos_end }) +} + +fn construct_table(prefix: &str, timings: Timings, total_millis: f64) -> String { + let header = format!("{prefix} Benchmarks"); + + let mut lines: Vec = vec![ + MARKER.into(), + header, + String::new(), + "| Day | Part 1 | Part 2 |".into(), + "| :---: | :---: | :---: |".into(), + ]; + + for timing in timings.data { + let path = get_path_for_bin(timing.day); + lines.push(format!( + "| [Day {}]({}) | `{}` | `{}` |", + timing.day.into_inner(), + path, + timing.part_1.unwrap_or_else(|| "-".into()), + timing.part_2.unwrap_or_else(|| "-".into()) + )); + } + + lines.push(String::new()); + lines.push(format!("**Total: {total_millis:.2}ms**")); + lines.push(MARKER.into()); + + lines.join("\n") +} + +fn update_content(s: &mut String, timings: Timings, total_millis: f64) -> Result<(), Error> { + let positions = locate_table(s)?; + let table = construct_table("##", timings, total_millis); + s.replace_range(positions.pos_start..positions.pos_end, &table); + Ok(()) +} + +pub fn update(timings: Timings) -> Result<(), Error> { + let path = "README.md"; + let mut readme = String::from_utf8_lossy(&fs::read(path)?).to_string(); + let total_millis = timings.total_millis(); + update_content(&mut readme, timings, total_millis)?; + fs::write(path, &readme)?; + Ok(()) +} + +#[cfg(feature = "test_lib")] +mod tests { + use super::{update_content, MARKER}; + use crate::{day, template::timings::Timing, template::timings::Timings}; + + fn get_mock_timings() -> Timings { + Timings { + data: vec![ + Timing { + day: day!(1), + part_1: Some("10ms".into()), + part_2: Some("20ms".into()), + total_nanos: 3e+10, + }, + Timing { + day: day!(2), + part_1: Some("30ms".into()), + part_2: Some("40ms".into()), + total_nanos: 7e+10, + }, + Timing { + day: day!(4), + part_1: Some("40ms".into()), + part_2: Some("50ms".into()), + total_nanos: 9e+10, + }, + ], + } + } + + #[test] + #[should_panic] + fn errors_if_marker_not_present() { + let mut s = "# readme".to_string(); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + } + + #[test] + #[should_panic] + fn errors_if_too_many_markers_present() { + let mut s = format!("{} {} {}", MARKER, MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + } + + #[test] + fn updates_empty_benchmarks() { + let mut s = format!("foo\nbar\n{}{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + assert_eq!(s.contains("## Benchmarks"), true); + } + + #[test] + fn updates_existing_benchmarks() { + let mut s = format!("foo\nbar\n{}{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + assert_eq!(s.matches(MARKER).collect::>().len(), 2); + assert_eq!(s.matches("## Benchmarks").collect::>().len(), 1); + } + + #[test] + fn format_benchmarks() { + let mut s = format!("foo\nbar\n{}\n{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + let expected = [ + "foo", + "bar", + "", + "## Benchmarks", + "", + "| Day | Part 1 | Part 2 |", + "| :---: | :---: | :---: |", + "| [Day 1](./src/bin/01.rs) | `10ms` | `20ms` |", + "| [Day 2](./src/bin/02.rs) | `30ms` | `40ms` |", + "| [Day 4](./src/bin/04.rs) | `40ms` | `50ms` |", + "", + "**Total: 190.00ms**", + "", + "baz", + ] + .join("\n"); + assert_eq!(s, expected); + } +} diff --git a/2023/src/template/run_multi.rs b/2023/src/template/run_multi.rs new file mode 100644 index 0000000..5bafefb --- /dev/null +++ b/2023/src/template/run_multi.rs @@ -0,0 +1,256 @@ +use std::{collections::HashSet, io}; + +use crate::template::{Day, ANSI_BOLD, ANSI_ITALIC, ANSI_RESET}; + +use super::{ + all_days, + timings::{Timing, Timings}, +}; + +pub fn run_multi(days_to_run: &HashSet, is_release: bool, is_timed: bool) -> Option { + let mut timings: Vec = Vec::with_capacity(days_to_run.len()); + + let mut need_space = false; + + // NOTE: use non-duplicate, sorted day values. + all_days() + .filter(|day| days_to_run.contains(day)) + .for_each(|day| { + if need_space { + println!(); + } + need_space = true; + + println!("{ANSI_BOLD}Day {day}{ANSI_RESET}"); + println!("------"); + + let output = child_commands::run_solution(day, is_timed, is_release).unwrap(); + + if output.is_empty() { + println!("Not solved."); + } else { + let val = child_commands::parse_exec_time(&output, day); + timings.push(val); + } + }); + + if is_timed { + let timings = Timings { data: timings }; + let total_millis = timings.total_millis(); + println!( + "\n{ANSI_BOLD}Total (Run):{ANSI_RESET} {ANSI_ITALIC}{total_millis:.2}ms{ANSI_RESET}" + ); + Some(timings) + } else { + None + } +} + +#[derive(Debug)] +pub enum Error { + BrokenPipe, + IO(io::Error), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IO(e) + } +} + +#[must_use] +pub fn get_path_for_bin(day: Day) -> String { + format!("./src/bin/{day}.rs") +} + +/// All solutions live in isolated binaries. +/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output. +pub mod child_commands { + use super::{get_path_for_bin, Error}; + use crate::template::Day; + use std::{ + io::{BufRead, BufReader}, + path::Path, + process::{Command, Stdio}, + thread, + }; + + /// Run the solution bin for a given day + pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result, Error> { + // skip command invocation for days that have not been scaffolded yet. + if !Path::new(&get_path_for_bin(day)).exists() { + return Ok(vec![]); + } + + let day_padded = day.to_string(); + let mut args = vec!["run", "--quiet", "--bin", &day_padded]; + + if is_release { + args.push("--release"); + } + + if is_timed { + // mirror `--time` flag to child invocations. + args.push("--"); + args.push("--time"); + } + + // spawn child command with piped stdout/stderr. + // forward output to stdout/stderr while grabbing stdout lines. + + let mut cmd = Command::new("cargo") + .args(&args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let stdout = BufReader::new(cmd.stdout.take().ok_or(super::Error::BrokenPipe)?); + let stderr = BufReader::new(cmd.stderr.take().ok_or(super::Error::BrokenPipe)?); + + let mut output = vec![]; + + let thread = thread::spawn(move || { + stderr.lines().for_each(|line| { + eprintln!("{}", line.unwrap()); + }); + }); + + for line in stdout.lines() { + let line = line.unwrap(); + println!("{line}"); + output.push(line); + } + + thread.join().unwrap(); + cmd.wait()?; + + Ok(output) + } + + pub fn parse_exec_time(output: &[String], day: Day) -> super::Timing { + let mut timings = super::Timing { + day, + part_1: None, + part_2: None, + total_nanos: 0_f64, + }; + + output + .iter() + .filter_map(|l| { + if !l.contains(" samples)") { + return None; + } + + let Some((timing_str, nanos)) = parse_time(l) else { + eprintln!("Could not parse timings from line: {l}"); + return None; + }; + + let part = l.split(':').next()?; + Some((part, timing_str, nanos)) + }) + .for_each(|(part, timing_str, nanos)| { + if part.contains("Part 1") { + timings.part_1 = Some(timing_str.into()); + } else if part.contains("Part 2") { + timings.part_2 = Some(timing_str.into()); + } + + timings.total_nanos += nanos; + }); + + timings + } + + fn parse_to_float(s: &str, postfix: &str) -> Option { + s.split(postfix).next()?.parse().ok() + } + + fn parse_time(line: &str) -> Option<(&str, f64)> { + // for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200 + let str_timing = line + .split(" samples)") + .next()? + .split('(') + .last()? + .split('@') + .next()? + .trim(); + + let parsed_timing = match str_timing { + s if s.contains("ns") => s.split("ns").next()?.parse::().ok(), + s if s.contains("ยตs") => parse_to_float(s, "ยตs").map(|x| x * 1000_f64), + s if s.contains("ms") => parse_to_float(s, "ms").map(|x| x * 1_000_000_f64), + s => parse_to_float(s, "s").map(|x| x * 1_000_000_000_f64), + }?; + + Some((str_timing, parsed_timing)) + } + + /// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333 + #[cfg(feature = "test_lib")] + 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(feature = "test_lib")] + mod tests { + use super::parse_exec_time; + + use crate::day; + + #[test] + fn parses_execution_times() { + let res = parse_exec_time( + &[ + "Part 1: 0 (74.13ns @ 100000 samples)".into(), + "Part 2: 10 (74.13ms @ 99999 samples)".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 74130074.13_f64); + assert_eq!(res.part_1.unwrap(), "74.13ns"); + assert_eq!(res.part_2.unwrap(), "74.13ms"); + } + + #[test] + fn parses_with_patterns_in_input() { + let res = parse_exec_time( + &[ + "Part 1: @ @ @ ( ) ms (2s @ 5 samples)".into(), + "Part 2: 10s (100ms @ 1 samples)".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 2100000000_f64); + assert_eq!(res.part_1.unwrap(), "2s"); + assert_eq!(res.part_2.unwrap(), "100ms"); + } + + #[test] + fn parses_missing_parts() { + let res = parse_exec_time( + &[ + "Part 1: โœ– ".into(), + "Part 2: โœ– ".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 0_f64); + assert_eq!(res.part_1.is_none(), true); + assert_eq!(res.part_2.is_none(), true); + } + } +} diff --git a/2023/src/template/runner.rs b/2023/src/template/runner.rs new file mode 100644 index 0000000..b4e41bc --- /dev/null +++ b/2023/src/template/runner.rs @@ -0,0 +1,174 @@ +/// Encapsulates code that interacts with solution functions. +use std::fmt::Display; +use std::hint::black_box; +use std::io::{stdout, Write}; +use std::process::Output; +use std::time::{Duration, Instant}; +use std::{cmp, env, process}; + +use crate::template::ANSI_BOLD; +use crate::template::{aoc_cli, Day, ANSI_ITALIC, ANSI_RESET}; + +pub fn run_part(func: impl Fn(I) -> Option, input: I, day: Day, part: u8) { + let part_str = format!("Part {part}"); + + let (result, duration, samples) = + run_timed(func, input, |result| print_result(result, &part_str, "")); + + print_result(&result, &part_str, &format_duration(&duration, samples)); + + if let Some(result) = result { + submit_result(result, day, part); + } +} + +/// Run a solution part. The behavior differs depending on whether we are running a release or debug build: +/// 1. in debug, the function is executed once. +/// 2. in release, the function is benched (approx. 1 second of execution time or 10 samples, whatever take longer.) +fn run_timed( + func: impl Fn(I) -> T, + input: I, + hook: impl Fn(&T), +) -> (T, Duration, u128) { + let timer = Instant::now(); + let result = { + let input = input.clone(); + + #[cfg(feature = "dhat-heap")] + let _profiler = dhat::Profiler::new_heap(); + + func(input) + }; + let base_time = timer.elapsed(); + + hook(&result); + + let run = if std::env::args().any(|x| x == "--time") { + bench(func, input, &base_time) + } else { + (base_time, 1) + }; + + (result, run.0, run.1) +} + +fn bench(func: impl Fn(I) -> T, input: I, base_time: &Duration) -> (Duration, u128) { + let mut stdout = stdout(); + + print!(" > {ANSI_ITALIC}benching{ANSI_RESET}"); + let _ = stdout.flush(); + + let bench_iterations = cmp::min( + 10000, + cmp::max( + Duration::from_secs(1).as_nanos() / cmp::max(base_time.as_nanos(), 10), + 10, + ), + ); + + let mut timers: Vec = vec![]; + + for _ in 0..bench_iterations { + // need a clone here to make the borrow checker happy. + let cloned = input.clone(); + let timer = Instant::now(); + black_box(func(black_box(cloned))); + timers.push(timer.elapsed()); + } + + ( + #[allow(clippy::cast_possible_truncation)] + Duration::from_nanos(average_duration(&timers) as u64), + bench_iterations, + ) +} + +fn average_duration(numbers: &[Duration]) -> u128 { + numbers + .iter() + .map(std::time::Duration::as_nanos) + .sum::() + / numbers.len() as u128 +} + +fn format_duration(duration: &Duration, samples: u128) -> String { + if samples == 1 { + format!(" ({duration:.1?})") + } else { + format!(" ({duration:.1?} @ {samples} samples)") + } +} + +fn print_result(result: &Option, part: &str, duration_str: &str) { + let is_intermediate_result = duration_str.is_empty(); + + match result { + Some(result) => { + if result.to_string().contains('\n') { + let str = format!("{part}: โ–ผ {duration_str}"); + if is_intermediate_result { + print!("{str}"); + } else { + print!("\r"); + println!("{str}"); + println!("{result}"); + } + } else { + let str = format!("{part}: {ANSI_BOLD}{result}{ANSI_RESET}{duration_str}"); + if is_intermediate_result { + print!("{str}"); + } else { + print!("\r"); + println!("{str}"); + } + } + } + None => { + if is_intermediate_result { + print!("{part}: โœ–"); + } else { + print!("\r"); + println!("{part}: โœ– "); + } + } + } +} + +/// Parse the arguments passed to `solve` and try to submit one part of the solution if: +/// 1. we are in `--release` mode. +/// 2. aoc-cli is installed. +fn submit_result( + result: T, + day: Day, + part: u8, +) -> Option> { + let args: Vec = env::args().collect(); + + if !args.contains(&"--submit".into()) { + return None; + } + + 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 Ok(part_submit) = args[part_index].parse::() else { + eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1"); + process::exit(1); + }; + + if part_submit != part { + return None; + } + + 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); + } + + println!("Submitting result via aoc-cli..."); + Some(aoc_cli::submit(day, part, &result.to_string())) +} diff --git a/2023/src/template/timings.rs b/2023/src/template/timings.rs new file mode 100644 index 0000000..194464e --- /dev/null +++ b/2023/src/template/timings.rs @@ -0,0 +1,391 @@ +use std::{collections::HashMap, fs, io::Error, str::FromStr}; +use tinyjson::JsonValue; + +use crate::template::Day; + +static TIMINGS_FILE_PATH: &str = "./data/timings.json"; + +/// Represents benchmark times for a single day. +#[derive(Clone, Debug)] +pub struct Timing { + pub day: Day, + pub part_1: Option, + pub part_2: Option, + pub total_nanos: f64, +} + +/// Represents benchmark times for a set of days. +/// Can be serialized from / to JSON. +#[derive(Clone, Debug, Default)] +pub struct Timings { + pub data: Vec, +} + +impl Timings { + /// Dehydrate timings to a JSON file. + pub fn store_file(&self) -> Result<(), Error> { + let json = JsonValue::from(self.clone()); + let mut file = fs::File::create(TIMINGS_FILE_PATH)?; + json.format_to(&mut file) + } + + /// Rehydrate timings from a JSON file. If not present, returns empty timings. + pub fn read_from_file() -> Self { + let s = fs::read_to_string(TIMINGS_FILE_PATH) + .map_err(|x| x.to_string()) + .and_then(Timings::try_from); + + match s { + Ok(timings) => timings, + Err(e) => { + eprintln!("{e}"); + Timings::default() + } + } + } + + /// Merge two sets of timings, overwriting `self` with `other` if present. + pub fn merge(&self, new: &Self) -> Self { + let mut data: Vec = vec![]; + + for timing in &new.data { + data.push(timing.clone()); + } + + for timing in &self.data { + if !data.iter().any(|t| t.day == timing.day) { + data.push(timing.clone()); + } + } + + data.sort_unstable_by(|a, b| a.day.cmp(&b.day)); + Timings { data } + } + + /// Sum up total duration of timings as millis. + pub fn total_millis(&self) -> f64 { + self.data.iter().map(|x| x.total_nanos).sum::() / 1_000_000_f64 + } + + pub fn is_day_complete(&self, day: Day) -> bool { + self.data + .iter() + .any(|t| t.day == day && t.part_1.is_some() && t.part_2.is_some()) + } +} + +/* -------------------------------------------------------------------------- */ + +impl From for JsonValue { + fn from(value: Timings) -> Self { + let mut map: HashMap = HashMap::new(); + + map.insert( + "data".into(), + JsonValue::Array(value.data.iter().map(JsonValue::from).collect()), + ); + + JsonValue::Object(map) + } +} + +impl TryFrom for Timings { + type Error = String; + + fn try_from(value: String) -> Result { + let json = JsonValue::from_str(&value).or(Err("not valid JSON file."))?; + + let json_data = json + .get::>() + .ok_or("expected JSON document to be an object.")? + .get("data") + .ok_or("expected JSON document to have key `data`.")? + .get::>() + .ok_or("expected `json.data` to be an array.")?; + + Ok(Timings { + data: json_data + .iter() + .map(Timing::try_from) + .collect::>()?, + }) + } +} + +/* -------------------------------------------------------------------------- */ + +impl From<&Timing> for JsonValue { + fn from(value: &Timing) -> Self { + let mut map: HashMap = HashMap::new(); + + map.insert("day".into(), JsonValue::String(value.day.to_string())); + map.insert("total_nanos".into(), JsonValue::Number(value.total_nanos)); + + let part_1 = value.part_1.clone().map(JsonValue::String); + let part_2 = value.part_2.clone().map(JsonValue::String); + + map.insert( + "part_1".into(), + match part_1 { + Some(x) => x, + None => JsonValue::Null, + }, + ); + + map.insert( + "part_2".into(), + match part_2 { + Some(x) => x, + None => JsonValue::Null, + }, + ); + + JsonValue::Object(map) + } +} + +impl TryFrom<&JsonValue> for Timing { + type Error = String; + + fn try_from(value: &JsonValue) -> Result { + let json = value + .get::>() + .ok_or("Expected timing to be a JSON object.")?; + + let day = json + .get("day") + .and_then(|v| v.get::()) + .and_then(|day| Day::from_str(day).ok()) + .ok_or("Expected timing.day to be a Day struct.")?; + + let part_1 = json + .get("part_1") + .map(|v| if v.is_null() { None } else { v.get::() }) + .ok_or("Expected timing.part_1 to be null or string.")?; + + let part_2 = json + .get("part_2") + .map(|v| if v.is_null() { None } else { v.get::() }) + .ok_or("Expected timing.part_2 to be null or string.")?; + + let total_nanos = json + .get("total_nanos") + .and_then(|v| v.get::().copied()) + .ok_or("Expected timing.total_nanos to be a number.")?; + + Ok(Timing { + day, + part_1: part_1.cloned(), + part_2: part_2.cloned(), + total_nanos, + }) + } +} + +/* -------------------------------------------------------------------------- */ + +#[cfg(feature = "test_lib")] +mod tests { + use crate::day; + + use super::{Timing, Timings}; + + fn get_mock_timings() -> Timings { + Timings { + data: vec![ + Timing { + day: day!(1), + part_1: Some("10ms".into()), + part_2: Some("20ms".into()), + total_nanos: 3e+10, + }, + Timing { + day: day!(2), + part_1: Some("30ms".into()), + part_2: Some("40ms".into()), + total_nanos: 7e+10, + }, + Timing { + day: day!(4), + part_1: Some("40ms".into()), + part_2: None, + total_nanos: 4e+10, + }, + ], + } + } + + mod deserialization { + use crate::{day, template::timings::Timings}; + + #[test] + fn handles_json_timings() { + let json = r#"{ "data": [{ "day": "01", "part_1": "1ms", "part_2": null, "total_nanos": 1000000000 }] }"#.to_string(); + let timings = Timings::try_from(json).unwrap(); + assert_eq!(timings.data.len(), 1); + let timing = timings.data.first().unwrap(); + assert_eq!(timing.day, day!(1)); + assert_eq!(timing.part_1, Some("1ms".to_string())); + assert_eq!(timing.part_2, None); + assert_eq!(timing.total_nanos, 1_000_000_000_f64); + } + + #[test] + fn handles_empty_timings() { + let json = r#"{ "data": [] }"#.to_string(); + let timings = Timings::try_from(json).unwrap(); + assert_eq!(timings.data.len(), 0); + } + + #[test] + #[should_panic] + fn panics_for_invalid_json() { + let json = r#"{}"#.to_string(); + Timings::try_from(json).unwrap(); + } + + #[test] + #[should_panic] + fn panics_for_malformed_timings() { + let json = r#"{ "data": [{ "day": "01" }, { "day": "26" }, { "day": "02", "part_2": null, "total_nanos": 0 }] }"#.to_string(); + Timings::try_from(json).unwrap(); + } + } + + mod serialization { + use super::get_mock_timings; + use std::collections::HashMap; + use tinyjson::JsonValue; + + #[test] + fn serializes_timings() { + let timings = get_mock_timings(); + let value = JsonValue::try_from(timings).unwrap(); + assert_eq!( + value + .get::>() + .unwrap() + .get("data") + .unwrap() + .get::>() + .unwrap() + .len(), + 3 + ); + } + } + + mod is_day_complete { + use crate::{ + day, + template::timings::{Timing, Timings}, + }; + + #[test] + fn handles_completed_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: Some("1ms".into()), + part_2: Some("2ms".into()), + total_nanos: 3_000_000_000_f64, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), true); + } + + #[test] + fn handles_partial_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: Some("1ms".into()), + part_2: None, + total_nanos: 1_000_000_000_f64, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), false); + } + + #[test] + fn handles_uncompleted_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: None, + part_2: None, + total_nanos: 0.0, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), false); + } + } + + mod merge { + use crate::{ + day, + template::timings::{Timing, Timings}, + }; + + use super::get_mock_timings; + + #[test] + fn handles_disjunct_timings() { + let timings = get_mock_timings(); + let other = Timings { + data: vec![Timing { + day: day!(3), + part_1: None, + part_2: None, + total_nanos: 0_f64, + }], + }; + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 4); + assert_eq!(merged.data[0].day, day!(1)); + assert_eq!(merged.data[1].day, day!(2)); + assert_eq!(merged.data[2].day, day!(3)); + assert_eq!(merged.data[3].day, day!(4)); + } + + #[test] + fn handles_overlapping_timings() { + let timings = get_mock_timings(); + + let other = Timings { + data: vec![Timing { + day: day!(2), + part_1: None, + part_2: None, + total_nanos: 0_f64, + }], + }; + let merged = timings.merge(&other); + + assert_eq!(merged.data.len(), 3); + assert_eq!(merged.data[0].day, day!(1)); + assert_eq!(merged.data[1].day, day!(2)); + assert_eq!(merged.data[1].total_nanos, 0_f64); + assert_eq!(merged.data[2].day, day!(4)); + } + + #[test] + fn handles_empty_timings() { + let timings = Timings::default(); + let other = get_mock_timings(); + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 3); + } + + #[test] + fn handles_empty_other_timings() { + let timings = get_mock_timings(); + let other = Timings::default(); + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 3); + } + } +}