1
0
Fork 0

switch to different AoC template

main
Andrew Coleman 2024-02-09 16:47:40 -05:00
parent 562cd63d87
commit c6e8a2d19f
85 changed files with 3045 additions and 746 deletions

12
2023/.cargo/config.toml Normal file
View File

@ -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"

17
2023/.editorconfig Normal file
View File

@ -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

31
2023/.gitignore vendored
View File

@ -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

525
2023/Cargo.lock generated
View File

@ -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"

View File

@ -1,18 +1,33 @@
[package]
name = "aoc2023"
version = "0.1.0"
name = "advent_of_code"
version = "0.11.0"
authors = ["Andrew Coleman <penguincoder@gmail.com>"]
edition = "2021"
default-run = "advent_of_code"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[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

21
2023/LICENSE Normal file
View File

@ -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.

1
2023/README.md Normal file
View File

@ -0,0 +1 @@
Template from https://github.com/fspoettel/advent-of-code-rust

0
2023/data/examples/.keep Normal file
View File

View File

@ -0,0 +1,7 @@
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen

View File

@ -0,0 +1,4 @@
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

View File

@ -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

10
2023/data/examples/03.txt Normal file
View File

@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

View File

@ -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

33
2023/data/examples/05.txt Normal file
View File

@ -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

View File

@ -0,0 +1,2 @@
Time: 7 15 30
Distance: 9 40 200

View File

@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,3 @@
0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45

View File

@ -0,0 +1,5 @@
..F7.
.FJ|.
SJ.L7
|F--J
LJ...

View File

@ -0,0 +1,9 @@
..........
.S------7.
.|F----7|.
.||....||.
.||....||.
.|L-7F-J|.
.|..||..|.
.L--JL--J.
..........

View File

@ -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...

View File

@ -0,0 +1,5 @@
.....
.S-7.
.|.|.
.L-J.
.....

10
2023/data/examples/11.txt Normal file
View File

@ -0,0 +1,10 @@
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....

View File

@ -0,0 +1,6 @@
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1

View File

@ -0,0 +1,41 @@
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
.#.##.#.#
.##..##..
.#.##.#..
#......##
#......##
.#.##.#..
.##..##.#
#..#....#
###..##..
.##.#####
.##.#####
###..##..
#..#....#
#..##...#
#.##..##.
..#.##.#.
##..#...#
##...#..#
..#.##.#.
..##..##.
#.#.##.#.

16
2023/data/examples/13.txt Normal file
View File

@ -0,0 +1,16 @@
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#

10
2023/data/examples/14.txt Normal file
View File

@ -0,0 +1,10 @@
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....

View File

@ -0,0 +1 @@
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7

10
2023/data/examples/16.txt Normal file
View File

@ -0,0 +1,10 @@
.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....

13
2023/data/examples/17.txt Normal file
View File

@ -0,0 +1,13 @@
2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533

14
2023/data/examples/18.txt Normal file
View File

@ -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)

17
2023/data/examples/19.txt Normal file
View File

@ -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}

View File

@ -0,0 +1,5 @@
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output

View File

@ -0,0 +1,5 @@
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a

11
2023/data/examples/21.txt Normal file
View File

@ -0,0 +1,11 @@
...........
.....###.#.
.###.##..#.
..#.#...#..
....#.#....
.##..S####.
.##..#...#.
.......##..
.##.#.####.
.##..##.##.
...........

View File

View File

View File

View File

0
2023/data/inputs/.keep Normal file
View File

0
2023/data/puzzles/.keep Normal file
View File

0
2023/src/bin/.keep Normal file
View File

View File

@ -1,4 +1,4 @@
use aoc_runner_derive::aoc;
advent_of_code::solution!(1);
fn parse(input: &str) -> Vec<u32> {
input
@ -16,15 +16,13 @@ fn parse(input: &str) -> Vec<u32> {
.collect()
}
#[aoc(day1, part1)]
fn part1_sum(input: &str) -> u32 {
pub fn part_one(input: &str) -> Option<u32> {
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<u32> {
Some(sub_letters_for_digits(input).iter().sum())
}
fn char_to_num(c: char) -> Option<u32> {
@ -101,37 +99,15 @@ fn sub_letters_for_digits(input: &str) -> Vec<u32> {
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));
}
}

View File

@ -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<Game> {
input
.lines()
@ -81,17 +80,32 @@ fn parse(input: &str) -> Vec<Game> {
.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<u32> {
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<u32> {
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));
}
}

View File

@ -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<u32>>;
@ -16,7 +17,6 @@ fn parse(input: &str) -> Vec<Vec<char>> {
.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<u32> {
let mut sum: u32 = 0;
for (_coord, plist) in input.iter() {
for (_coord, plist) in associate_parts(input).iter() {
sum += plist.iter().sum::<u32>();
}
sum
Some(sum)
}
#[aoc(day3, part2)]
fn part2(input: &PartList) -> u32 {
pub fn part_two(input: &str) -> Option<u32> {
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::<u32>();
}
}
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));
}
}

View File

@ -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<Card> {
input
.lines()
@ -54,11 +54,10 @@ fn parse(input: &str) -> Vec<Card> {
.collect()
}
#[aoc(day4, part1)]
fn part1(input: &[Card]) -> i32 {
pub fn part_one(input: &str) -> Option<u32> {
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<u32> {
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));
}
}

View File

@ -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<RangeMap> {
rm
}
#[aoc_generator(day5)]
fn parse(input: &str) -> Almanac {
let mut lines = input.lines();
let seeds = lines.next().unwrap().split(": ").collect::<Vec<&str>>()[1]
@ -102,13 +102,13 @@ fn min_location(seeds: &Vec<u64>, 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<u64> {
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<u64> {
let input = parse(input_str);
let mut seeds: Vec<u64> = 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));
}
}

View File

@ -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<Race> {
let mut lines = input.lines();
let times: Vec<u64> = lines.next().unwrap().split(": ").collect::<Vec<&str>>()[1]
@ -39,19 +38,18 @@ fn parse(input: &str) -> Vec<Race> {
.collect()
}
#[aoc(day6, part1)]
fn part1(input: &[Race]) -> u64 {
input
pub fn part_one(input: &str) -> Option<u64> {
Some(parse(input)
.iter()
.map(|race| race.winning_times().len() as u64)
.collect::<Vec<u64>>()
.iter()
.product()
.product())
}
#[aoc(day6, part2)]
fn part2(input: &[Race]) -> u64 {
let time = input
pub fn part_two(input: &str) -> Option<u64> {
let races = parse(input);
let time = races
.iter()
.map(|race| race.time)
.collect::<Vec<u64>>()
@ -61,7 +59,7 @@ fn part2(input: &[Race]) -> u64 {
.join("")
.parse::<u64>()
.unwrap();
let distance = input
let distance = races
.iter()
.map(|race| race.distance)
.collect::<Vec<u64>>()
@ -72,21 +70,22 @@ fn part2(input: &[Race]) -> u64 {
.parse::<u64>()
.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));
}
}

View File

@ -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<Hand> {
input
.lines()
@ -254,21 +254,19 @@ fn parse(input: &str) -> Vec<Hand> {
.collect()
}
#[aoc(day7, part1)]
fn part1(input: &[Hand]) -> u64 {
pub fn part_one(input: &str) -> Option<u32> {
let mut sum: u64 = 0;
let mut hands = input.iter().map(|h| h.clone()).collect::<Vec<Hand>>();
let mut hands = parse(input).iter().map(|h| h.clone()).collect::<Vec<Hand>>();
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<u32> {
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));
}
}

View File

@ -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<u64> {
Some(parse(input).traverse())
}
#[aoc(day8, part2)]
fn part2(input: &str) -> u64 {
parse(input).ghost_traverse()
pub fn part_two(input: &str) -> Option<u64> {
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));
}
}

View File

@ -1,18 +1,17 @@
use aoc_runner_derive::{aoc, aoc_generator};
advent_of_code::solution!(9);
type Seq = Vec<i64>;
type Seq = Vec<i32>;
fn all_zero(s: &Seq) -> bool {
s.iter().find(|t| **t != 0).is_none()
}
#[aoc_generator(day9)]
fn parse(input: &str) -> Vec<Seq> {
input
.lines()
.filter_map(|line| {
if !line.is_empty() {
let nums = line.split(" ").map(|n| n.parse::<i64>().unwrap()).collect();
let nums = line.split(" ").map(|n| n.parse::<i32>().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<u32> {
Some(parse(input).iter().map(|s| next_number(&s)).sum::<i32>().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<u32> {
Some(parse(input).iter().map(|s| previous_number(&s)).sum::<i32>().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));
}
}

View File

@ -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<Step>, map: &Map) -> Step {
}
}
#[aoc(day10, part1)]
fn part1(input: &MetalIsland) -> u64 {
pub fn part_one(input_str: &str) -> Option<u32> {
let input = parse(input_str);
let next_steps = start_steps(&input.start, &input.map);
let mut left_path: Vec<Step> = 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<u32> {
let input = parse(input_str);
let next_steps = start_steps(&input.start, &input.map);
let mut path: Vec<Step> = 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));
}
}

View File

@ -1,4 +1,4 @@
use aoc_runner_derive::aoc;
advent_of_code::solution!(11);
struct Galaxy(Vec<Coord>);
@ -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<u32> {
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<u32> {
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
);
}
}

View File

@ -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<usize>,
}
#[aoc_generator(day12)]
fn parse(input: &str) -> Vec<Spring> {
input
.lines()
@ -114,18 +114,20 @@ fn calc_damaged_solutions(list: &Vec<Condition>, groups: &Vec<usize>, cache: &mu
result
}
#[aoc(day12, part1)]
fn part1(input: &[Spring]) -> usize {
pub fn part_one(input: &str) -> Option<u32> {
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<u64> {
part2(&parse(input))
}
fn part2(input: &[Spring]) -> Option<u64> {
let mut result: usize = 0;
for src in input.iter() {
let mut l: Vec<Condition> = 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));
}
}

View File

@ -1,4 +1,4 @@
use aoc_runner_derive::{aoc, aoc_generator};
advent_of_code::solution!(13);
type Board = Vec<Vec<char>>;
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<Map> {
let mut result: Vec<Map> = vec![];
let mut cur: Board = vec![];
@ -224,91 +223,36 @@ fn parse(input: &str) -> Vec<Map> {
result
}
#[aoc(day13, part1)]
fn part1(input: &[Map]) -> usize {
input.iter().map(|t| t.part1()).sum()
pub fn part_one(input: &str) -> Option<u32> {
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<u32> {
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);
}
}

View File

@ -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<String> {
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::<Vec<_>>().len() * (total_lines - index);
}
score
score.try_into().unwrap()
}
#[aoc(day14, part1)]
fn part1(input: &[String]) -> usize {
pub fn part_one(input: &str) -> Option<u32> {
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<u32> {
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));
}
}

View File

@ -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<usize> {
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<usize> {
let mut boxes: Vec<Vec<(&str, usize)>> = 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));
}
}

View File

@ -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<Vec<char>> {
input
.lines()
@ -134,22 +134,22 @@ fn energized_squares(visited: &HashSet<(i64, i64, Dir)>) -> usize {
t.len()
}
#[aoc(day16, part1)]
fn part1(input: &[Vec<char>]) -> usize {
pub fn part_one(input_str: &str) -> Option<usize> {
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<char>]) -> usize {
pub fn part_two(input_str: &str) -> Option<usize> {
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<char>]) -> 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<char>]) -> 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<char>]) -> 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<char>]) -> 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));
}
}

View File

@ -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<usize> {
input
.lines()
@ -98,38 +98,27 @@ fn continue_in_direction<const MIN: usize>(map: &Matrix<usize>, p: &Path) -> Vec
}
}
#[aoc(day17, part1)]
fn part1(input: &Matrix<usize>) -> usize {
find_path::<1, 3>(&input)
pub fn part_one(input: &str) -> Option<usize> {
Some(find_path::<1, 3>(&parse(input)))
}
#[aoc(day17, part2)]
fn part2(input: &Matrix<usize>) -> usize {
find_path::<4, 10>(&input)
pub fn part_two(input: &str) -> Option<usize> {
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));
}
}

View File

@ -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<Move> {
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<isize> {
Some(shoelace(&get_perimeter(&parse(input))))
}
#[aoc(day18, part2)]
fn part2(input: &[Move]) -> isize {
let moves = input
pub fn part_two(input: &str) -> Option<isize> {
let moves = parse(input)
.iter()
.map(|m| {
let mut s = m.color.clone();
@ -92,32 +90,22 @@ fn part2(input: &[Move]) -> isize {
}
})
.collect::<Vec<_>>();
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));
}
}

View File

@ -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<u64> {
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<u64> {
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));
}
}

View File

@ -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<String, Machine> {
r
}
#[aoc(day20, part1)]
fn part1(input: &str) -> u64 {
pub fn part_one(input: &str) -> Option<u64> {
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<u64> {
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));
}
}

View File

@ -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<Point, usize> {
visited
}
#[aoc(day21, part1)]
fn part1(input: &(Point, Map)) -> usize {
pub fn part_one(input_str: &str) -> Option<usize> {
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<usize> {
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));
}
}

26
2023/src/bin/22.rs Normal file
View File

@ -0,0 +1,26 @@
advent_of_code::solution!(22);
pub fn part_one(input: &str) -> Option<u32> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
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);
}
}

26
2023/src/bin/23.rs Normal file
View File

@ -0,0 +1,26 @@
advent_of_code::solution!(23);
pub fn part_one(input: &str) -> Option<u32> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
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);
}
}

26
2023/src/bin/24.rs Normal file
View File

@ -0,0 +1,26 @@
advent_of_code::solution!(24);
pub fn part_one(input: &str) -> Option<u32> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
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);
}
}

26
2023/src/bin/25.rs Normal file
View File

@ -0,0 +1,26 @@
advent_of_code::solution!(25);
pub fn part_one(input: &str) -> Option<u32> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
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);
}
}

View File

@ -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.

View File

@ -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<u8>,
},
All {
release: bool,
},
Time {
all: bool,
day: Option<Day>,
store: bool,
},
#[cfg(feature = "today")]
Today,
}
pub fn parse() -> Result<AppArguments, Box<dyn std::error::Error>> {
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)
}
};
}
},
};
}

26
2023/src/template.txt Normal file
View File

@ -0,0 +1,26 @@
advent_of_code::solution!(%DAY_NUMBER%);
pub fn part_one(input: &str) -> Option<u32> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
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);
}
}

View File

@ -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<Output, AocCommandError> {
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<Output, AocCommandError> {
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<Output, AocCommandError> {
// 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<u16> {
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<String> {
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<Output, AocCommandError> {
// 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))
}
}

View File

@ -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);
}

View File

@ -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);
};
}

View File

@ -0,0 +1,6 @@
pub mod all;
pub mod download;
pub mod read;
pub mod scaffold;
pub mod solve;
pub mod time;

View File

@ -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);
};
}

View File

@ -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<File, std::io::Error> {
OpenOptions::new().write(true).create_new(true).open(path)
}
fn create_file(path: &str) -> Result<File, std::io::Error> {
OpenOptions::new().write(true).create(true).open(path)
}
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.");
}

View File

@ -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<u8>) {
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();
}

View File

@ -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<Day>, 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.");
}
}
}
}

192
2023/src/template/day.rs Normal file
View File

@ -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<Self> {
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<Self> {
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<u8> for Day {
fn eq(&self, other: &u8) -> bool {
self.0.eq(other)
}
}
impl PartialOrd<u8> for Day {
fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
/* -------------------------------------------------------------------------- */
impl FromStr for Day {
type Err = DayFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<Self::Item> {
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);
}
}
/* -------------------------------------------------------------------------- */

68
2023/src/template/mod.rs Normal file
View File

@ -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); )*
}
};
}

View File

@ -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 = "<!--- benchmarking table --->";
#[derive(Debug)]
pub enum Error {
Parser(String),
IO(io::Error),
}
impl From<std::io::Error> 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<TablePosition, Error> {
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<String> = 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::<Vec<&str>>().len(), 2);
assert_eq!(s.matches("## Benchmarks").collect::<Vec<&str>>().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",
"<!--- benchmarking table --->",
"## 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**",
"<!--- benchmarking table --->",
"baz",
]
.join("\n");
assert_eq!(s, expected);
}
}

View File

@ -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<Day>, is_release: bool, is_timed: bool) -> Option<Timings> {
let mut timings: Vec<Timing> = 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<std::io::Error> 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<Vec<String>, 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<f64> {
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::<f64>().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);
}
}
}

174
2023/src/template/runner.rs Normal file
View File

@ -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<I: Clone, T: Display>(func: impl Fn(I) -> Option<T>, 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<I: Clone, T>(
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<I: Clone, T>(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<Duration> = 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::<u128>()
/ 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<T: Display>(result: &Option<T>, 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<T: Display>(
result: T,
day: Day,
part: u8,
) -> Option<Result<Output, aoc_cli::AocCommandError>> {
let args: Vec<String> = 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::<u8>() 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()))
}

View File

@ -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<String>,
pub part_2: Option<String>,
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<Timing>,
}
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<Timing> = 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::<f64>() / 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<Timings> for JsonValue {
fn from(value: Timings) -> Self {
let mut map: HashMap<String, JsonValue> = HashMap::new();
map.insert(
"data".into(),
JsonValue::Array(value.data.iter().map(JsonValue::from).collect()),
);
JsonValue::Object(map)
}
}
impl TryFrom<String> for Timings {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
let json = JsonValue::from_str(&value).or(Err("not valid JSON file."))?;
let json_data = json
.get::<HashMap<String, JsonValue>>()
.ok_or("expected JSON document to be an object.")?
.get("data")
.ok_or("expected JSON document to have key `data`.")?
.get::<Vec<JsonValue>>()
.ok_or("expected `json.data` to be an array.")?;
Ok(Timings {
data: json_data
.iter()
.map(Timing::try_from)
.collect::<Result<_, _>>()?,
})
}
}
/* -------------------------------------------------------------------------- */
impl From<&Timing> for JsonValue {
fn from(value: &Timing) -> Self {
let mut map: HashMap<String, JsonValue> = 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<Self, Self::Error> {
let json = value
.get::<HashMap<String, JsonValue>>()
.ok_or("Expected timing to be a JSON object.")?;
let day = json
.get("day")
.and_then(|v| v.get::<String>())
.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::<String>() })
.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::<String>() })
.ok_or("Expected timing.part_2 to be null or string.")?;
let total_nanos = json
.get("total_nanos")
.and_then(|v| v.get::<f64>().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::<HashMap<String, JsonValue>>()
.unwrap()
.get("data")
.unwrap()
.get::<Vec<JsonValue>>()
.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);
}
}
}