diff --git a/.gitignore b/.gitignore index 2934158..3badaa1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ tags # Persistent undo [._]*.un~ .vscode/settings.json + +input.txt diff --git a/2023/.gitignore b/2023/.gitignore new file mode 100644 index 0000000..3badaa1 --- /dev/null +++ b/2023/.gitignore @@ -0,0 +1,50 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +#Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ +.vscode/settings.json + +input.txt diff --git a/2023/Cargo.lock b/2023/Cargo.lock new file mode 100644 index 0000000..b527531 --- /dev/null +++ b/2023/Cargo.lock @@ -0,0 +1,904 @@ +# This file is automatically @generated by Cargo. +# 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 = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[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 = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + +[[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 = "day-1" +version = "2023.0.0" +dependencies = [ + "derive-getters", + "error-stack", + "itertools", + "log", + "nom", +] + +[[package]] +name = "day-10" +version = "2023.0.0" +dependencies = [ + "glam", + "itertools", + "nom", + "nom_locate", + "rstest", +] + +[[package]] +name = "day-11" +version = "2023.0.0" +dependencies = [ + "glam", + "itertools", + "nom", + "nom_locate", +] + +[[package]] +name = "day-2" +version = "2023.0.0" +dependencies = [ + "itertools", + "log", + "nom", +] + +[[package]] +name = "day-3" +version = "2023.0.0" +dependencies = [ + "itertools", + "log", +] + +[[package]] +name = "day-4" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", + "rstest", + "rstest_reuse", +] + +[[package]] +name = "day-5" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", +] + +[[package]] +name = "day-6" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", +] + +[[package]] +name = "day-7" +version = "2023.0.0" +dependencies = [ + "dhat", + "itertools", + "nom", + "num", + "num-traits", +] + +[[package]] +name = "day-8" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", + "rstest", +] + +[[package]] +name = "day-9" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", + "rstest", +] + +[[package]] +name = "derive-getters" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2c35ab6e03642397cdda1dd58abbc05d418aef8e36297f336d5aba060fe8df" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "error-stack" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a72baa257b5e0e2de241967bc5ee8f855d6072351042688621081d66b2a76b" +dependencies = [ + "anyhow", + "rustc_version", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[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 = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.39", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" +dependencies = [ + "quote", + "rand", + "rustc_version", + "syn 2.0.39", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-info" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/2023/Cargo.toml b/2023/Cargo.toml new file mode 100644 index 0000000..58fbf08 --- /dev/null +++ b/2023/Cargo.toml @@ -0,0 +1,26 @@ +[workspace] +resolver = "2" +members = [ "day-*" ] + +[workspace.package] +version = "2023.0.0" +edition = "2021" +authors = [ "Dylan Thies" ] +repository = "https://github.com/smellyfis/AOC-2023.git" + +[workspace.dependencies] +derive-getters = "0.3.0" +error-stack = "0.4.1" +itertools = "0.12.0" +log = "0.4.20" +nom = "7.1.3" +nom_locate= "4.2.0" +rstest = "0.18.2" +rstest_reuse = "0.6.0" +dhat = "0.3.2" +glam = "0.24.2" + + +[profile.dhat] +inherits = "release" +debug = true diff --git a/2023/day-1/Cargo.toml b/2023/day-1/Cargo.toml new file mode 100644 index 0000000..b8d9042 --- /dev/null +++ b/2023/day-1/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "day-1" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +derive-getters.workspace = true +error-stack.workspace = true +itertools.workspace = true +log.workspace = true +nom.workspace = true diff --git a/2023/day-1/src/lib.rs b/2023/day-1/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2023/day-1/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2023/day-1/src/main.rs b/2023/day-1/src/main.rs new file mode 100644 index 0000000..52f4bcd --- /dev/null +++ b/2023/day-1/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_1::part1::part1; +use day_1::part2::part2; + +fn main() { + let input = include_str!("./input.txt"); + let (_, part1_result) = part1(input).unwrap(); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-1/src/part1.rs b/2023/day-1/src/part1.rs new file mode 100644 index 0000000..59e06eb --- /dev/null +++ b/2023/day-1/src/part1.rs @@ -0,0 +1,59 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + self, + character::complete::{alphanumeric1, newline}, + multi::separated_list1, +}; + +/// Day-1 part 1 of AC2023 +/// +/// # Arguments +/// - input the input for day1 as a string +/// +/// # Panics +/// This panics whenever a number isn't present in a line of the input +/// +/// # Errors +/// errors when can't parse the input +pub fn part1(input: &str) -> nom::IResult<&str, String> { + let (_, values) = parse_input(input)?; + println!("{values:?}"); + Ok(( + "", + values + .iter() + .map(|v| v.first().expect("always at least one number") * 10 + v.last().expect("always atleast one number")) + .sum::() + .to_string(), + )) +} + +fn parse_input(input: &str) -> nom::IResult<&str, Vec>> { + let (i, j) = separated_list1(newline, alphanumeric1)(input)?; + let res = j + .iter() + .map(|v| { + v.chars() + .filter_map(|x| x.to_digit(10)) + .collect::>() + }) + .collect::>>(); + Ok((i, res)) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet"; + + #[test] + fn part1_works() { + let (_, result) = part1(INPUT).unwrap(); + assert_eq!(result, "142".to_string()); + } +} diff --git a/2023/day-1/src/part2.rs b/2023/day-1/src/part2.rs new file mode 100644 index 0000000..11991ac --- /dev/null +++ b/2023/day-1/src/part2.rs @@ -0,0 +1,70 @@ +#![warn(clippy::all, clippy::pedantic)] + +/// Day 1 Part 2 of AOC2023 +/// +/// # Arguments +/// - puzzle input +/// +/// # Panics +/// this panics if there is no numbers in a line +pub fn part2(input: &str) -> String { + let values = input.lines().map(parse_line).collect::>>(); + println!("{values:?}"); + values + .iter() + .map(|v| v.first().expect("There is always at least one number") * 10 + v.last().expect("there is always at least one number")) + .sum::() + .to_string() +} + +fn parse_line(line: &str) -> Vec { + (0..line.len()) + .filter_map(|index| { + let reduced_line = &line[index..]; + let result = if reduced_line.starts_with("one") { + Some(1) + } else if reduced_line.starts_with("two") { + Some(2) + } else if reduced_line.starts_with("three") { + Some(3) + } else if reduced_line.starts_with("four") { + Some(4) + } else if reduced_line.starts_with("five") { + Some(5) + } else if reduced_line.starts_with("six") { + Some(6) + } else if reduced_line.starts_with("seven") { + Some(7) + } else if reduced_line.starts_with("eight") { + Some(8) + } else if reduced_line.starts_with("nine") { + Some(9) + } else if reduced_line.starts_with("zero") { + Some(0) + } else { + reduced_line.chars().next().expect("there is alwayss a character").to_digit(10) + }; + + result + }) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "281".to_string()); + } +} diff --git a/2023/day-10/Cargo.toml b/2023/day-10/Cargo.toml new file mode 100644 index 0000000..eb82395 --- /dev/null +++ b/2023/day-10/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "day-10" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +nom_locate = {workspace = true } +itertools.workspace = true +glam = "0.24.2" + +[dev-dependencies] +rstest = {workspace = true} diff --git a/2023/day-10/src/lib.rs b/2023/day-10/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-10/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-10/src/main.rs b/2023/day-10/src/main.rs new file mode 100644 index 0000000..58a1fe7 --- /dev/null +++ b/2023/day-10/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_10::part1; +use day_10::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-10/src/part1.rs b/2023/day-10/src/part1.rs new file mode 100644 index 0000000..65ee1c7 --- /dev/null +++ b/2023/day-10/src/part1.rs @@ -0,0 +1,267 @@ +#![warn(clippy::all, clippy::pedantic)] + +use std::{collections::HashMap, iter::successors}; + +use glam::IVec2; +use itertools::Itertools; +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + combinator::eof, + multi::{fold_many1, many1}, + sequence::terminated, + IResult, Parser, +}; +use nom_locate::LocatedSpan; + +type Span<'a> = LocatedSpan<&'a str>; +type SpanIVec2<'a> = LocatedSpan<&'a str, IVec2>; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +enum PipeFrom { + Up, + Down, + Left, + Right, +} +impl PipeFrom { + fn from_ivecs(a: IVec2, b: IVec2) -> Option { + match (a - b).into() { + (0, 1) => Some(Self::Down), + (0, -1) => Some(Self::Up), + (1, 0) => Some(Self::Right), + (-1, 0) => Some(Self::Left), + _ => None, + //value => unimplemented!("this can't be {a:?} - {b:?} = {value:?}"), + } + } + fn to_ivec(self) -> IVec2 { + match self { + PipeFrom::Up => (0, -1).into(), + PipeFrom::Down => (0, 1).into(), + PipeFrom::Left => (-1, 0).into(), + PipeFrom::Right => (1, 0).into(), + } + } +} + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +enum PipeType { + // 'S' + Start, + // '-' + Horizontal, + // '|' + Vertical, + // 'F' + DownRight, + // '7' + DownLeft, + // 'L' + UpRight, + // 'J' + UpLeft, + // '.' -this is so we can parse but should be discarded + None, +} + +impl PipeType { + fn get_adjacents(self) -> Vec { + match self { + PipeType::Start => vec![(-1, 0).into(), (0, -1).into(), (0, 1).into(), (1, 0).into()], + PipeType::Horizontal => vec![(1, 0).into(), (-1, 0).into()], + PipeType::Vertical => vec![(0, 1).into(), (0, -1).into()], + PipeType::DownRight => vec![(0, 1).into(), (1, 0).into()], + PipeType::DownLeft => vec![(0, 1).into(), (-1, 0).into()], + PipeType::UpRight => vec![(0, -1).into(), (1, 0).into()], + PipeType::UpLeft => vec![(0, -1).into(), (-1, 0).into()], + PipeType::None => unimplemented!("this should never have been called"), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +struct Pipe { + pub pipe_type: PipeType, + pub position: IVec2, +} + +impl Pipe { + fn get_adjacent(&self) -> Vec<(IVec2, PipeFrom)> { + self.pipe_type + .get_adjacents() + .into_iter() + .map(|x| x + self.position) + .filter_map(|x| PipeFrom::from_ivecs(self.position, x).map(|y| (x, y))) + .collect() + } + fn next(&self, from: PipeFrom) -> IVec2 { + use PipeFrom::{Down, Left, Right, Up}; + use PipeType::{DownLeft, DownRight, Horizontal, UpLeft, UpRight, Vertical}; + match (from, self.pipe_type) { + (Up, Vertical) | (Left, DownLeft) | (Right, DownRight) => Down, + (Up, UpLeft) | (Down, DownLeft) | (Right, Horizontal) => Left, + (Up, UpRight) | (Down, DownRight) | (Left, Horizontal) => Right, + (Down, Vertical) | (Left, UpLeft) | (Right, UpRight) => Up, + _ => unimplemented!("no"), + } + .to_ivec() + + self.position + } +} + +/// day 10 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part1(input: &str) -> String { + let input = Span::new(input); + let (_, grid) = parse_input(input).expect("aoc always parse"); + let start_node = grid + .values() + .find(|x| x.pipe_type == PipeType::Start) + .expect("has a start"); + + (successors( + Some( + start_node + .get_adjacent() + .iter() + .filter_map(|(x, from)| grid.get(x).map(|y| (y, *from))) + .filter(|(x, _)| { + x.get_adjacent() + .iter() + .map(|(y, _)| y) + .contains(&start_node.position) + }) + .collect::>(), + ), + |front_nodes| { + Some( + front_nodes + .iter() + .filter_map(|(pipe, from)| { + grid.get(&pipe.next(*from)) + .map(|x| (x, PipeFrom::from_ivecs(pipe.position, x.position).unwrap())) + }) + .collect::>(), + ) + }, + ) + .filter(|x| !x.is_empty()) + .position(|a| a[0].0 == a[1].0) + .unwrap() + + 1) + .to_string() + //todo!() +} + +fn with_xy(span: Span) -> SpanIVec2 { + let x = i32::try_from(span.get_column()).expect("overflow") - 1; + let y = i32::try_from(span.location_line()).expect("wrap around") - 1; + span.map_extra(|()| IVec2::new(x, y)) +} + +fn parse_pipe(input: Span) -> IResult { + alt(( + tag("S").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Start, + position: position.extra, + }), + tag("-").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Horizontal, + position: position.extra, + }), + tag("|").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Vertical, + position: position.extra, + }), + tag("F").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::DownRight, + position: position.extra, + }), + tag("7").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::DownLeft, + position: position.extra, + }), + tag("L").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::UpRight, + position: position.extra, + }), + tag("J").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::UpLeft, + position: position.extra, + }), + tag(".").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::None, + position: position.extra, + }), + ))(input) +} + +fn parse_input(input: Span) -> IResult> { + fold_many1( + terminated(many1(parse_pipe), alt((complete::line_ending, eof))), + HashMap::new, + |mut acc, x| { + x.into_iter() + .filter(|x| x.pipe_type != PipeType::None) + .for_each(|x| { + acc.insert(x.position, x); + }); + acc + }, + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + use rstest::rstest; + + #[rstest] + #[case( + "..... +.S-7. +.|.|. +.L-J. +.....", + "4" + )] + #[case( + "-L|F7 +7S-7| +L|7|| +-L-J| +L|-JF", + "4" + )] + #[case( + "..F7. +.FJ|. +SJ.L7 +|F--J +LJ...", + "8" + )] + #[case( + "7-F7- +.FJ|7 +SJLL7 +|F--J +LJ.LJ", + "8" + )] + + fn part1_works(#[case] input: &str, #[case] expected: &str) { + let result = part1(input); + assert_eq!(result, expected); + } +} diff --git a/2023/day-10/src/part2.rs b/2023/day-10/src/part2.rs new file mode 100644 index 0000000..8c38439 --- /dev/null +++ b/2023/day-10/src/part2.rs @@ -0,0 +1,375 @@ +#![warn(clippy::all, clippy::pedantic)] + +use std::{collections::HashMap, fmt::Display, iter::successors}; + +use glam::IVec2; +use itertools::Itertools; +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + combinator::eof, + multi::{fold_many1, many1}, + sequence::terminated, + IResult, Parser, +}; +use nom_locate::LocatedSpan; + +type Span<'a> = LocatedSpan<&'a str>; +type SpanIVec2<'a> = LocatedSpan<&'a str, IVec2>; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +enum PipeFrom { + Up, + Down, + Left, + Right, +} +impl PipeFrom { + fn from_ivecs(a: IVec2, b: IVec2) -> Option { + match (a - b).into() { + (0, 1) => Some(Self::Down), + (0, -1) => Some(Self::Up), + (1, 0) => Some(Self::Right), + (-1, 0) => Some(Self::Left), + _ => None, + //value => unimplemented!("this can't be {a:?} - {b:?} = {value:?}"), + } + } + fn to_ivec(self) -> IVec2 { + match self { + PipeFrom::Up => (0, -1).into(), + PipeFrom::Down => (0, 1).into(), + PipeFrom::Left => (-1, 0).into(), + PipeFrom::Right => (1, 0).into(), + } + } +} + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +enum PipeType { + // 'S' + Start, + // '-' + Horizontal, + // '|' + Vertical, + // 'F' + DownRight, + // '7' + DownLeft, + // 'L' + UpRight, + // 'J' + UpLeft, + // '.' -this is so we can parse but should be discarded + None, + Outer, + Inner, +} + +impl PipeType { + fn get_adjacents(self) -> Vec { + match self { + PipeType::Start => vec![(-1, 0).into(), (0, -1).into(), (0, 1).into(), (1, 0).into()], + PipeType::Horizontal => vec![(1, 0).into(), (-1, 0).into()], + PipeType::Vertical => vec![(0, 1).into(), (0, -1).into()], + PipeType::DownRight => vec![(0, 1).into(), (1, 0).into()], + PipeType::DownLeft => vec![(0, 1).into(), (-1, 0).into()], + PipeType::UpRight => vec![(0, -1).into(), (1, 0).into()], + PipeType::UpLeft => vec![(0, -1).into(), (-1, 0).into()], + value => unimplemented!("this should never have been called for type {value:?}"), + } + } +} +impl Display for PipeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Start => "S", + Self::Horizontal => "-", + Self::Vertical => "|", + Self::DownRight => "F", + Self::DownLeft => "7", + Self::UpRight => "L", + Self::UpLeft => "J", + Self::None => ".", + Self::Outer => "O", + Self::Inner => "I", + } + ) + } +} + +#[derive(Debug, Eq, PartialEq)] +struct Pipe { + pub pipe_type: PipeType, + pub position: IVec2, +} + +impl Pipe { + fn get_adjacent(&self) -> Vec<(IVec2, PipeFrom)> { + self.pipe_type + .get_adjacents() + .into_iter() + .map(|x| x + self.position) + .filter_map(|x| PipeFrom::from_ivecs(self.position, x).map(|y| (x, y))) + .collect() + } + fn next(&self, from: PipeFrom) -> IVec2 { + use PipeFrom::{Down, Left, Right, Up}; + use PipeType::{DownLeft, DownRight, Horizontal, UpLeft, UpRight, Vertical}; + match (from, self.pipe_type) { + (Up, Vertical) | (Left, DownLeft) | (Right, DownRight) => Down, + (Up, UpLeft) | (Down, DownLeft) | (Right, Horizontal) => Left, + (Up, UpRight) | (Down, DownRight) | (Left, Horizontal) => Right, + (Down, Vertical) | (Left, UpLeft) | (Right, UpRight) => Up, + _ => unimplemented!("no"), + } + .to_ivec() + + self.position + } +} + +/// day 10 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str) -> String { + let input = Span::new(input); + let (_, grid) = parse_input(input).expect("aoc always parse"); + let start_node = grid + .values() + .find(|x| x.pipe_type == PipeType::Start) + .expect("has a start"); + let start_node_true_type = match &start_node + .get_adjacent() + .iter() + .filter_map(|(x, from)| grid.get(x).map(|y| (y, *from))) + .filter_map(|(x, from)| { + x.get_adjacent() + .iter() + .map(|(y, _)| y) + .contains(&start_node.position) + .then_some(from) + }) + .collect::>()[..] + { + [PipeFrom::Up, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Up] => PipeType::DownRight, + [PipeFrom::Up, PipeFrom::Right] | [PipeFrom::Right, PipeFrom::Up] => PipeType::DownLeft, + [PipeFrom::Down, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Down] => PipeType::UpRight, + [PipeFrom::Down, PipeFrom::Right] | [PipeFrom::Right, PipeFrom::Down] => PipeType::UpLeft, + [PipeFrom::Up, PipeFrom::Down] | [PipeFrom::Down, PipeFrom::Up] => PipeType::Vertical, + [PipeFrom::Right, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Right] => { + PipeType::Horizontal + } + _ => PipeType::Start, + }; + + let mut pieces = HashMap::new(); + pieces.insert(start_node.position, start_node_true_type); + + successors( + Some( + start_node + .get_adjacent() + .iter() + .filter_map(|(x, from)| grid.get(x).map(|y| (y, *from))) + .filter(|(x, _)| { + x.get_adjacent() + .iter() + .map(|(y, _)| y) + .contains(&start_node.position) + }) + .collect::>(), + ), + |front_nodes| { + if front_nodes[0].0 == front_nodes[1].0 { + return None; + } + Some( + front_nodes + .iter() + .filter_map(|(pipe, from)| { + grid.get(&pipe.next(*from)) + .map(|x| (x, PipeFrom::from_ivecs(pipe.position, x.position).unwrap())) + }) + .collect::>(), + ) + }, + ) + .filter(|x| !x.is_empty()) + .for_each(|x| { + for (pipe, _) in &x { + pieces.insert(pipe.position, pipe.pipe_type); + } + }); + let corners = pieces.keys().fold( + ((i32::MAX, i32::MAX), (i32::MIN, i32::MIN)), + |((minimum_x, min_y), (maximal_x, max_y)), pos| { + let minimum_x = minimum_x.min(pos.x); + let miny = min_y.min(pos.y); + let maximal_x = maximal_x.max(pos.x); + let maxy = max_y.max(pos.y); + ((minimum_x, miny), (maximal_x, maxy)) + }, + ); + /* Debug + (corners.0 .1..=corners.1 .1).for_each(|y| { + (corners.0.0..=corners.1.0).for_each(|x| { + let p = pieces.get(&(x,y).into()).unwrap_or(&PipeType::None); + print!("{p}"); + }); + print!("\n"); + }); + */ + (corners.0 .1..=corners.1 .1).for_each(|y| { + let mut status = false; + (corners.0 .0..=corners.1 .0) + .map(|x| IVec2::new(x, y)) + .for_each(|pos| { + if let Some(piece) = pieces.get(&pos) { + status = match piece { + PipeType::Vertical | PipeType::DownRight | PipeType::DownLeft => !status, + _ => status, + }; + } else if status { + pieces.insert(pos, PipeType::Inner); + } else { + pieces.insert(pos, PipeType::Outer); + } + }); + }); + /* Debug + println!(); + (corners.0 .1..=corners.1 .1).for_each(|y| { + (corners.0.0..=corners.1.0).for_each(|x| { + let p = pieces.get(&(x,y).into()).unwrap(); + print!("{p}"); + }); + print!("\n"); + }); + */ + pieces + .values() + .filter(|x| **x == PipeType::Inner) + .count() + .to_string() +} + +fn with_xy(span: Span) -> SpanIVec2 { + let x = i32::try_from(span.get_column()).expect("overflow") - 1; + let y = i32::try_from(span.location_line()).expect("wrap around") - 1; + span.map_extra(|()| IVec2::new(x, y)) +} + +fn parse_pipe(input: Span) -> IResult { + alt(( + tag("S").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Start, + position: position.extra, + }), + tag("-").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Horizontal, + position: position.extra, + }), + tag("|").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::Vertical, + position: position.extra, + }), + tag("F").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::DownRight, + position: position.extra, + }), + tag("7").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::DownLeft, + position: position.extra, + }), + tag("L").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::UpRight, + position: position.extra, + }), + tag("J").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::UpLeft, + position: position.extra, + }), + tag(".").map(with_xy).map(|position| Pipe { + pipe_type: PipeType::None, + position: position.extra, + }), + ))(input) +} + +fn parse_input(input: Span) -> IResult> { + fold_many1( + terminated(many1(parse_pipe), alt((complete::line_ending, eof))), + HashMap::new, + |mut acc, x| { + x.into_iter() + .filter(|x| x.pipe_type != PipeType::None) + .for_each(|x| { + acc.insert(x.position, x); + }); + acc + }, + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + use rstest::rstest; + + #[rstest] + #[case( + "........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +...........", + "4" + )] + #[case( + ".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...", + "8" + )] + #[case( + "FF7FSF7F7F7F7F7F---7 +L|LJ||||||||||||F--J +FL-7LJLJ||||||LJL-77 +F--JF--7||LJLJ7F7FJ- +L---JF-JLJ.||-FJLJJ7 +|F|F-JF---7F7-L7L|7| +|FFJF7L7F-JF7|JL---7 +7-L-JL7||F7|L7F-7F7| +L.L7LFJ|||||FJL7||LJ +L7JLJL-JLJLJL--JLJ.L", + "10" + )] + + fn part2_works(#[case] input: &str, #[case] expected: &str) { + let result = part2(input); + assert_eq!(result, expected); + } +} diff --git a/2023/day-11/Cargo.toml b/2023/day-11/Cargo.toml new file mode 100644 index 0000000..5dd44c5 --- /dev/null +++ b/2023/day-11/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "day-11" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = { workspace = true } +itertools = {workspace = true } +glam = {workspace = true} +nom_locate = {workspace = true } diff --git a/2023/day-11/src/lib.rs b/2023/day-11/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-11/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-11/src/main.rs b/2023/day-11/src/main.rs new file mode 100644 index 0000000..4a2069b --- /dev/null +++ b/2023/day-11/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_11::part1; +use day_11::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input, 1_000_000); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-11/src/part1.rs b/2023/day-11/src/part1.rs new file mode 100644 index 0000000..b5e25cc --- /dev/null +++ b/2023/day-11/src/part1.rs @@ -0,0 +1,94 @@ +#![warn(clippy::all, clippy::pedantic)] + +use glam::IVec2; +use itertools::Itertools; +use std::collections::HashSet; + +#[must_use] +pub fn part1(input: &str) -> String { + let points = parse_input(input); + let ((min_x, min_y), (mut max_x, /*mut*/ max_y)) = points.iter().fold( + ((i32::MAX, i32::MAX), (i32::MIN, i32::MIN)), + |((min_x, min_y), (max_x, max_y)), pos| { + let min_x = min_x.min(pos.x); + let min_y = min_y.min(pos.y); + let max_x = max_x.max(pos.x); + let max_y = max_y.max(pos.y); + ((min_x, min_y), (max_x, max_y)) + }, + ); + let mut modifier = 0; + let mut adjusted_points = HashSet::new(); + for x in min_x..=max_x { + let column = (min_y..=max_y) + .filter_map(|y| points.get(&(x, y).into())) + .collect::>(); + if column.is_empty() { + modifier += 1; + } + for point in column { + adjusted_points.insert(*point + IVec2::new(modifier, 0)); + } + } + max_x += modifier; + + let mut modifier = 0; + let mut points = HashSet::new(); + for y in min_y..=max_y { + let row = (min_x..=max_x) + .filter_map(|x| adjusted_points.get(&(x, y).into())) + .collect::>(); + if row.is_empty() { + modifier += 1; + } + for point in row { + points.insert(*point + IVec2::new(0, modifier)); + } + } + //max_y += modifier; + (points + .iter() + .cartesian_product(points.iter()) + .filter_map(|(a, b)| (a != b).then_some(*a - *b)) + .map(|pos| pos.x.abs() + pos.y.abs()) + .sum::() + / 2) + .to_string() +} + +fn parse_input(input: &str) -> HashSet { + input + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.chars().enumerate().filter_map(move |(x, c)| { + (c != '.').then_some(IVec2::new( + i32::try_from(x).unwrap(), + i32::try_from(y).unwrap(), + )) + }) + }) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#....."; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "374".to_string()); + } +} diff --git a/2023/day-11/src/part2.rs b/2023/day-11/src/part2.rs new file mode 100644 index 0000000..4df48f8 --- /dev/null +++ b/2023/day-11/src/part2.rs @@ -0,0 +1,102 @@ +#![warn(clippy::all, clippy::pedantic)] + +use glam::I64Vec2; +use itertools::Itertools; +use std::collections::HashSet; + +/// day 11 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str, modr: i64) -> String { + let points = parse_input(input); + let ((min_x, min_y), (mut max_x, /*mut*/ max_y)) = points.iter().fold( + ((i64::MAX, i64::MAX), (i64::MIN, i64::MIN)), + |((min_x, min_y), (max_x, max_y)), pos| { + let min_x = min_x.min(pos.x); + let min_y = min_y.min(pos.y); + let max_x = max_x.max(pos.x); + let max_y = max_y.max(pos.y); + ((min_x, min_y), (max_x, max_y)) + }, + ); + let mut modifier = 0; + let mut adjusted_points = HashSet::new(); + for x in min_x..=max_x { + let column = (min_y..=max_y) + .filter_map(|y| points.get(&(x, y).into())) + .collect::>(); + if column.is_empty() { + modifier += modr - 1; + } + for point in column { + adjusted_points.insert(*point + I64Vec2::new(modifier, 0)); + } + } + max_x += modifier; + + let mut modifier = 0; + let mut points = HashSet::new(); + for y in min_y..=max_y { + let row = (min_x..=max_x) + .filter_map(|x| adjusted_points.get(&(x, y).into())) + .collect::>(); + if row.is_empty() { + modifier += modr - 1; + } + for point in row { + points.insert(*point + I64Vec2::new(0, modifier)); + } + } + //max_y += modifier; + (points + .iter() + .cartesian_product(points.iter()) + .filter_map(|(a, b)| (*a != *b).then_some(*a - *b)) + .map(|a| u64::try_from(a.x.abs() + a.y.abs()).unwrap()) + .sum::() + / 2) + .to_string() +} + +fn parse_input(input: &str) -> HashSet { + input + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.chars().enumerate().filter_map(move |(x, c)| { + (c != '.').then_some(I64Vec2::new( + i64::try_from(x).unwrap(), + i64::try_from(y).unwrap(), + )) + }) + }) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#....."; + + #[test] + fn part2_works() { + let result = part2(INPUT, 10); + assert_eq!(result, "1030".to_string()); + } +} diff --git a/2023/day-2/Cargo.toml b/2023/day-2/Cargo.toml new file mode 100644 index 0000000..ed23618 --- /dev/null +++ b/2023/day-2/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-2" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +log.workspace = true diff --git a/2023/day-2/src/lib.rs b/2023/day-2/src/lib.rs new file mode 100644 index 0000000..d807ddf --- /dev/null +++ b/2023/day-2/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::part1; +pub mod part2; +pub use crate::part2::part2; diff --git a/2023/day-2/src/main.rs b/2023/day-2/src/main.rs new file mode 100644 index 0000000..5950776 --- /dev/null +++ b/2023/day-2/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_2::part1; +use day_2::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-2/src/part1.rs b/2023/day-2/src/part1.rs new file mode 100644 index 0000000..7fd9088 --- /dev/null +++ b/2023/day-2/src/part1.rs @@ -0,0 +1,115 @@ +#![warn(clippy::all, clippy::pedantic)] + +use log::debug; +use nom::{ + bytes::complete::tag, + character::complete::{self, newline}, + multi::separated_list1, + sequence::{preceded, separated_pair}, +}; + +#[derive(Debug)] +struct Round { + pub red_n: u32, + pub green_n: u32, + pub blue_n: u32, +} + +#[derive(Debug)] +struct Game { + pub id: u32, + pub rounds: Vec, +} + +impl Game { + fn to_part1(&self) -> Option { + if self + .rounds + .iter() + .find_map(|r| { + //TODO if inverted use find_map + if r.red_n > 12 || r.green_n > 13 || r.blue_n > 14 { + Some(self.id) + } else { + None + } + }) + .is_some() + { + None + } else { + Some(self.id) + } + } +} + +/// part2 of day 2 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +pub fn part1(input: &str) -> String { + let (_, games) = process_input(input).expect("there should be input"); + debug!("{games:?}"); + games + .iter() + .filter_map(Game::to_part1) + .sum::() + .to_string() +} + +fn process_block(input: &str) -> nom::IResult<&str, (u32, String)> { + let (i, (cnt, color)) = + separated_pair(complete::u32, complete::space1, complete::alpha1)(input)?; + Ok((i, (cnt, color.to_owned()))) +} + +fn process_round(input: &str) -> nom::IResult<&str, Round> { + let (i, blocks) = separated_list1(tag(", "), process_block)(input)?; + let mut round = Round { + red_n: 0, + green_n: 0, + blue_n: 0, + }; + for (cnt, color) in blocks { + match color.as_str() { + "red" => round.red_n = cnt, + "green" => round.green_n = cnt, + "blue" => round.blue_n = cnt, + _ => panic!("this should be a color name"), + }; + } + Ok((i, round)) +} + +fn process_game(input: &str) -> nom::IResult<&str, Game> { + let (i, (id, rounds)) = separated_pair( + preceded(tag("Game "), complete::u32), + tag(": "), + separated_list1(tag("; "), process_round), + )(input)?; + Ok((i, Game { id, rounds })) +} + +fn process_input(input: &str) -> nom::IResult<&str, Vec> { + separated_list1(newline, process_game)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "8".to_string()); + } +} diff --git a/2023/day-2/src/part2.rs b/2023/day-2/src/part2.rs new file mode 100644 index 0000000..ea4a794 --- /dev/null +++ b/2023/day-2/src/part2.rs @@ -0,0 +1,106 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + bytes::complete::tag, + character::complete::{self, newline}, + multi::separated_list1, + sequence::{preceded, separated_pair}, +}; + +#[derive(Debug)] +struct Round { + pub red_n: u32, + pub green_n: u32, + pub blue_n: u32, +} + +#[derive(Debug)] +struct Game { + pub _id: u32, + pub rounds: Vec, +} + +impl Game { + fn to_power(&self) -> u64 { + let (r, g, b) = self.rounds.iter().fold((0_u64, 0_u64, 0_u64), |acc, x| { + let (mut val_r, mut val_g, mut val_b) = acc; + if u64::from(x.red_n) > acc.0 { + val_r = x.red_n.into(); + } + if u64::from(x.green_n) > acc.1 { + val_g = x.green_n.into(); + } + if u64::from(x.blue_n) > acc.2 { + val_b = x.blue_n.into(); + } + (val_r, val_g, val_b) + }); + r * g * b + } +} + +/// part2 of day 2 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +pub fn part2(input: &str) -> String { + let (_, games) = process_input(input).expect("there should be input"); + games.iter().map(Game::to_power).sum::().to_string() +} + +fn process_block(input: &str) -> nom::IResult<&str, (u32, String)> { + let (i, (cnt, color)) = + separated_pair(complete::u32, complete::space1, complete::alpha1)(input)?; + Ok((i, (cnt, color.to_owned()))) +} + +fn process_round(input: &str) -> nom::IResult<&str, Round> { + let (i, blocks) = separated_list1(tag(", "), process_block)(input)?; + let mut round = Round { + red_n: 0, + green_n: 0, + blue_n: 0, + }; + for (cnt, color) in blocks { + match color.as_str() { + "red" => round.red_n = cnt, + "green" => round.green_n = cnt, + "blue" => round.blue_n = cnt, + _ => panic!("this should be a color name"), + }; + } + Ok((i, round)) +} + +fn process_game(input: &str) -> nom::IResult<&str, Game> { + let (i, (id, rounds)) = separated_pair( + preceded(tag("Game "), complete::u32), + tag(": "), + separated_list1(tag("; "), process_round), + )(input)?; + Ok((i, Game { _id: id, rounds })) +} + +fn process_input(input: &str) -> nom::IResult<&str, Vec> { + separated_list1(newline, process_game)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "2286".to_string()); + } +} diff --git a/2023/day-3/Cargo.toml b/2023/day-3/Cargo.toml new file mode 100644 index 0000000..c3702ea --- /dev/null +++ b/2023/day-3/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day-3" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools.workspace = true +log.workspace = true diff --git a/2023/day-3/src/lib.rs b/2023/day-3/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-3/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-3/src/main.rs b/2023/day-3/src/main.rs new file mode 100644 index 0000000..5751c02 --- /dev/null +++ b/2023/day-3/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_3::part1; +use day_3::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-3/src/part1.rs b/2023/day-3/src/part1.rs new file mode 100644 index 0000000..f7d1f68 --- /dev/null +++ b/2023/day-3/src/part1.rs @@ -0,0 +1,129 @@ +#![warn(clippy::all, clippy::pedantic)] +use std::collections::BTreeMap; + +#[derive(Debug)] +struct SerialNumber { + pub no: u64, + pub start: (usize, usize), + pub end: (usize, usize), +} + +impl SerialNumber { + fn generate_adjacent(&self) -> Vec<(usize, usize)> { + let start_row = if self.start.0 == 0 { + 0 + } else { + self.start.0 - 1 + }; + let start_line = if self.start.1 == 0 { + 0 + } else { + self.start.1 - 1 + }; + (start_row..=(self.end.0 + 1)) + .flat_map(|x| (start_line..=(self.end.1 + 1)).map(move |y| (x, y))) + .collect() + } +} + +#[must_use] +pub fn part1(input: &str) -> String { + let (serials, symbols) = parse_input(input); + serials + .iter() + .filter(|x| { + x.generate_adjacent() + .iter() + .any(|t| symbols.get(t).is_some()) + }) + .map(|x| x.no) + .sum::() + .to_string() +} + +fn parse_input(input: &str) -> (Vec, BTreeMap<(usize, usize), char>) { + let mut numbers = Vec::new(); + let mut symbols = BTreeMap::new(); + for (line_no, line) in input.lines().enumerate() { + let mut prev_char = None; + let mut cur_no = 0_u64; + let mut cur_no_row_start = 0_usize; + for (row_no, c) in line.chars().enumerate() { + if let Some(d) = c.to_digit(10) { + if prev_char.is_some() { + cur_no = cur_no * 10 + u64::from(d); + } else { + cur_no = u64::from(d); + cur_no_row_start = row_no; + } + prev_char = Some(c); + } else { + if prev_char.is_some() { + //handle saving number off + numbers.push(SerialNumber { + no: cur_no, + start: (cur_no_row_start, line_no), + end: (row_no - 1, line_no), + }); + } + prev_char = None; + if c == '.' { + //move along space + continue; + } + //store symbol + let _ = symbols.insert((row_no, line_no), c); + } + } + //need to account for new line numbers + if prev_char.is_some() { + numbers.push(SerialNumber { + no: cur_no, + start: (cur_no_row_start, line_no), + end: (line.len() - 1, line_no), + }); + } + } + (numbers, symbols) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.."; + + const INPUT2: &str = "12.......*.. ++.........34 +.......-12.. +..78........ +..*....60... +78.........9 +.5.....23..$ +8...90*12... +............ +2.2......12. +.*.........* +1.1..503+.56"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "4361".to_string()); + } + + #[test] + fn part1_works_more() { + let result = part1(INPUT2); + assert_eq!(result, "925".to_string()); + } +} diff --git a/2023/day-3/src/part2.rs b/2023/day-3/src/part2.rs new file mode 100644 index 0000000..7699e40 --- /dev/null +++ b/2023/day-3/src/part2.rs @@ -0,0 +1,113 @@ +#![warn(clippy::all, clippy::pedantic)] +use std::collections::BTreeMap; + +#[derive(Debug)] +struct SerialNumber { + pub no: u64, + pub start: (usize, usize), + pub end: (usize, usize), +} + +impl SerialNumber { + fn is_adjacent(&self, pos: (usize, usize)) -> bool { + usize::abs_diff(self.start.1, pos.1) < 2 + && self.start.0 < 2 + pos.0 + && pos.0 < 2 + self.end.0 + } +} + +#[must_use] +pub fn part2(input: &str) -> String { + let (serials, symbols) = parse_input(input); + symbols + .iter() + .filter_map(|(key, value)| if *value == '*' { Some(*key) } else { None }) + .filter_map(|pos| { + let serials = serials + .iter() + .filter_map(|serial| { + if serial.is_adjacent(pos) { + Some(serial.no) + } else { + None + } + }) + .collect::>(); + if serials.len() == 2 { + Some(serials[0] * serials[1]) + } else { + None + } + }) + .sum::() + .to_string() + //find all serials next to '*' and map with '*' location +} + +fn parse_input(input: &str) -> (Vec, BTreeMap<(usize, usize), char>) { + let mut numbers = Vec::new(); + let mut symbols = BTreeMap::new(); + for (line_no, line) in input.lines().enumerate() { + let mut prev_char = None; + let mut cur_no = 0_u64; + let mut cur_no_row_start = 0_usize; + for (row_no, c) in line.chars().enumerate() { + if let Some(d) = c.to_digit(10) { + if prev_char.is_some() { + cur_no = cur_no * 10 + u64::from(d); + } else { + cur_no = u64::from(d); + cur_no_row_start = row_no; + } + prev_char = Some(c); + } else { + if prev_char.is_some() { + //handle saving number off + numbers.push(SerialNumber { + no: cur_no, + start: (cur_no_row_start, line_no), + end: (row_no - 1, line_no), + }); + } + prev_char = None; + if c == '.' { + //move along space + continue; + } + //store symbol + let _ = symbols.insert((row_no, line_no), c); + } + } + //need to account for new line numbers + if prev_char.is_some() { + numbers.push(SerialNumber { + no: cur_no, + start: (cur_no_row_start, line_no), + end: (line.len() - 1, line_no), + }); + } + } + (numbers, symbols) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.."; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "467835".to_string()); + } +} diff --git a/2023/day-4/Cargo.toml b/2023/day-4/Cargo.toml new file mode 100644 index 0000000..01cfc25 --- /dev/null +++ b/2023/day-4/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "day-4" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +rstest = {workspace = true} +rstest_reuse = {workspace = true} diff --git a/2023/day-4/src/lib.rs b/2023/day-4/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-4/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-4/src/main.rs b/2023/day-4/src/main.rs new file mode 100644 index 0000000..9f9225e --- /dev/null +++ b/2023/day-4/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_4::part1; +use day_4::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-4/src/part1.rs b/2023/day-4/src/part1.rs new file mode 100644 index 0000000..493f7fb --- /dev/null +++ b/2023/day-4/src/part1.rs @@ -0,0 +1,121 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + bytes::complete::tag, + character::complete, + multi::{fold_many1, separated_list1}, + sequence::{preceded, separated_pair, tuple}, + IResult, +}; +use std::collections::HashSet; + +struct Card { + pub _id: u8, + pub game_numbers: HashSet, + pub my_numbers: HashSet, +} + +impl Card { + fn get_win_count(&self) -> usize { + self.my_numbers.intersection(&self.game_numbers).count() + } + fn get_score(&self) -> Option { + let count = self.get_win_count(); + if count == 0 { + None + } else { + Some(2_usize.pow(u32::try_from(count).expect("shouldn't have a lot of cards") - 1)) + } + } +} + +/// day 4 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part1(input: &str) -> String { + let (_, cards) = parse_input(input).expect("there should be input"); + cards + .iter() + .filter_map(Card::get_score) + .sum::() + .to_string() +} + +fn parse_num_list(input: &str) -> IResult<&str, HashSet> { + fold_many1( + tuple((complete::u8, complete::space0)), + HashSet::new, + |mut acc, (x, _)| { + acc.insert(x); + acc + }, + )(input) +} + +fn parse_numbers(input: &str) -> IResult<&str, (HashSet, HashSet)> { + separated_pair( + parse_num_list, + tuple((tag("|"), complete::space1)), + parse_num_list, + )(input) +} + +fn parse_card(input: &str) -> IResult<&str, Card> { + let (input, (id, (my_numbers, game_numbers))) = separated_pair( + preceded(tuple((tag("Card"), complete::space1)), complete::u8), + tuple((tag(":"), complete::space1)), + parse_numbers, + )(input)?; + + Ok(( + input, + Card { + _id: id, + game_numbers, + my_numbers, + }, + )) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1(complete::line_ending, parse_card)(input) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case("Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53", Some(8))] + #[case("Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19", Some(2))] + #[case("Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1", Some(2))] + #[case("Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83", Some(1))] + #[case("Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36", None)] + #[case("Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", None)] + fn line_test(#[case] line: &str, #[case] expected: Option) { + let (input, card) = parse_card(line).expect("card should be parsed"); + assert_eq!(input, ""); + assert_eq!(card.get_score(), expected); + + } + + const INPUT: &str = "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 part1_works() { + let result = part1(INPUT); + assert_eq!(result, "13".to_string()); + } +} diff --git a/2023/day-4/src/part2.rs b/2023/day-4/src/part2.rs new file mode 100644 index 0000000..a9e96f2 --- /dev/null +++ b/2023/day-4/src/part2.rs @@ -0,0 +1,121 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + bytes::complete::tag, + character::complete, + combinator::map, + multi::{fold_many1, separated_list1}, + sequence::{preceded, separated_pair, tuple}, + IResult, +}; +use std::collections::{BTreeMap, HashSet}; + +struct Card { + pub id: usize, + pub game_numbers: HashSet, + pub my_numbers: HashSet, +} +impl Card { + fn get_win_count(&self) -> usize { + self.my_numbers.intersection(&self.game_numbers).count() + } +} + +/// day 4 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str) -> String { + let (_, cards) = parse_input(input).expect("there should be input"); + let mut cards_had = BTreeMap::new(); + for card in cards { + if let Some(x) = cards_had.get_mut(&card.id) { + *x += 1; + } else { + cards_had.insert(card.id, 1); + } + let next_id = card.id + 1; + let last_winner = card.id + card.get_win_count(); + //println!("{} - {next_id} {last_winner}", card.id); + if last_winner < next_id { + continue; + } + let card_count = *cards_had.get(&card.id).expect("already should have cards"); + for id in next_id..=last_winner { + if let Some(x) = cards_had.get_mut(&id) { + *x += card_count; + } else { + cards_had.insert(id, card_count); + } + } + } + //println!("{cards_had:#?}"); + + cards_had.values().sum::().to_string() +} + +fn parse_num_list(input: &str) -> IResult<&str, HashSet> { + fold_many1( + tuple((complete::u8, complete::space0)), + HashSet::new, + |mut acc, (x, _)| { + acc.insert(x); + acc + }, + )(input) +} + +fn parse_numbers(input: &str) -> IResult<&str, (HashSet, HashSet)> { + separated_pair( + parse_num_list, + tuple((tag("|"), complete::space1)), + parse_num_list, + )(input) +} + +fn parse_card(input: &str) -> IResult<&str, Card> { + let (input, (id, (my_numbers, game_numbers))) = separated_pair( + preceded( + tuple((tag("Card"), complete::space1)), + map(complete::u8, usize::from), + ), + tuple((tag(":"), complete::space1)), + parse_numbers, + )(input)?; + + Ok(( + input, + Card { + id, + game_numbers, + my_numbers, + }, + )) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1(complete::line_ending, parse_card)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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 part2_works() { + let result = part2(INPUT); + assert_eq!(result, "30".to_string()); + } +} diff --git a/2023/day-5/Cargo.toml b/2023/day-5/Cargo.toml new file mode 100644 index 0000000..df28fe3 --- /dev/null +++ b/2023/day-5/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day-5" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true diff --git a/2023/day-5/src/lib.rs b/2023/day-5/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-5/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-5/src/main.rs b/2023/day-5/src/main.rs new file mode 100644 index 0000000..5b6b8dd --- /dev/null +++ b/2023/day-5/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_5::part1; +use day_5::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-5/src/part1.rs b/2023/day-5/src/part1.rs new file mode 100644 index 0000000..48743d3 --- /dev/null +++ b/2023/day-5/src/part1.rs @@ -0,0 +1,203 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + bytes::complete::tag, + character::complete, + combinator::opt, + multi::separated_list1, + sequence::{separated_pair, terminated, tuple}, + IResult, +}; +use std::str::FromStr; + +#[derive(Debug, PartialEq, Eq)] +struct ParseTypeError; + +#[derive(Debug, PartialEq, Clone, Copy)] +enum Type { + Seed, + Soil, + Fertilizer, + Water, + Light, + Temperature, + Humidity, + Location, +} +impl FromStr for Type { + type Err = ParseTypeError; + fn from_str(s: &str) -> Result { + match s { + "seed" => Ok(Self::Seed), + "soil" => Ok(Self::Soil), + "fertilizer" => Ok(Self::Fertilizer), + "water" => Ok(Self::Water), + "light" => Ok(Self::Light), + "temperature" => Ok(Self::Temperature), + "humidity" => Ok(Self::Humidity), + "location" => Ok(Self::Location), + _ => Err(ParseTypeError), + } + } +} + +#[derive(Debug)] +struct ItemMapEntry { + pub to: u64, + pub from: u64, + pub count: u64, +} + +impl ItemMapEntry { + fn to_out(&self, from: u64) -> Option { + if from < self.from || self.from + self.count < from { + None + } else { + Some(self.to + (from - self.from)) + } + } +} + +#[derive(Debug)] +struct ItemMap { + pub from_type: Type, + pub to_type: Type, + pub mapping: Vec, +} + +impl ItemMap { + fn map(&self, from: u64) -> u64 { + self.mapping + .iter() + .find_map(|x| x.to_out(from)) + .or(Some(from)) + .expect("always") + } +} +/// part1 of day 5 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable + +#[must_use] +pub fn part1(input: &str) -> String { + let (_input, (mut to_process, maps)) = parse_input(input).expect("aoc always has input"); + //println!("{_input}"); + let mut from_type = Type::Seed; + while from_type != Type::Location { + let current_map = maps + .iter() + .find(|x| x.from_type == from_type) + .expect("should always find"); + to_process = to_process + .iter() + .map(|x| current_map.map(*x)) + .collect::>(); + //println!("{to_process:#?}"); + from_type = current_map.to_type; + } + //println!("{to_process:#?}"); + to_process + .iter() + .min() + .expect("always a number") + .to_string() +} + +fn parse_item_map_entry(input: &str) -> IResult<&str, ItemMapEntry> { + let (input, to) = complete::u64(input)?; + let (input, _) = complete::space1(input)?; + let (input, from) = complete::u64(input)?; + let (input, _) = complete::space1(input)?; + let (input, count) = complete::u64(input)?; + Ok((input, ItemMapEntry { to, from, count })) +} + +fn parse_to_from(input: &str) -> IResult<&str, (Type, Type)> { + let (input, (to_type, from_type)) = + separated_pair(complete::alpha1, tag("-to-"), complete::alpha1)(input)?; + Ok(( + input, + ( + to_type.parse().expect("there will be a to type"), + from_type.parse().expect("there will be a from type"), + ), + )) +} + +fn parse_map(input: &str) -> IResult<&str, ItemMap> { + let (input, (from_type, to_type)) = + terminated(parse_to_from, tuple((complete::space1, tag("map:"))))(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, mapping) = separated_list1(complete::line_ending, parse_item_map_entry)(input)?; + let (input, _) = opt(complete::line_ending)(input)?; + Ok(( + input, + ItemMap { + from_type, + to_type, + mapping, + }, + )) +} + +fn parse_seeds(input: &str) -> IResult<&str, Vec> { + let (input, _) = tag("seeds:")(input)?; + let (input, _) = complete::space1(input)?; + separated_list1(complete::space1, complete::u64)(input) +} + +fn parse_input(input: &str) -> IResult<&str, (Vec, Vec)> { + let (input, seeds) = terminated(parse_seeds, complete::line_ending)(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, maps) = separated_list1(complete::line_ending, parse_map)(input)?; + Ok((input, (seeds, maps))) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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 part1_works() { + let result = part1(INPUT); + assert_eq!(result, "35".to_string()); + } +} diff --git a/2023/day-5/src/part2.rs b/2023/day-5/src/part2.rs new file mode 100644 index 0000000..aad62f7 --- /dev/null +++ b/2023/day-5/src/part2.rs @@ -0,0 +1,284 @@ +#![warn(clippy::all, clippy::pedantic)] + +use core::ops::Range; +use itertools::Itertools; +use nom::{ + bytes::complete::tag, + character::complete, + combinator::opt, + multi::separated_list1, + sequence::{separated_pair, terminated, tuple}, + IResult, +}; +use std::str::FromStr; + +#[derive(Debug, PartialEq, Eq)] +struct ParseTypeError; + +#[derive(Debug, PartialEq, Clone, Copy)] +enum Type { + Seed, + Soil, + Fertilizer, + Water, + Light, + Temperature, + Humidity, + Location, +} +impl FromStr for Type { + type Err = ParseTypeError; + fn from_str(s: &str) -> Result { + match s { + "seed" => Ok(Self::Seed), + "soil" => Ok(Self::Soil), + "fertilizer" => Ok(Self::Fertilizer), + "water" => Ok(Self::Water), + "light" => Ok(Self::Light), + "temperature" => Ok(Self::Temperature), + "humidity" => Ok(Self::Humidity), + "location" => Ok(Self::Location), + _ => Err(ParseTypeError), + } + } +} + +#[derive(Debug)] +struct ItemMapEntry { + pub to: Range, + pub from: Range, +} + +#[derive(Debug)] +struct ItemMap { + pub from_type: Type, + pub to_type: Type, + pub mapping: Vec, +} + +impl ItemMap { + fn input_to_output(&self, input: &Range) -> Vec> { + /*if let Some(within) = self + .mapping + .iter() + .find(|x| x.from.contains(&input.start) && x.from.contains(&input.end)) + { + //fully contained + let offset = input.start - within.from.start; + let to_start = within.to.start + offset; + let to_end = to_start + u64::abs_diff(input.end, input.start); + if to_start == 0 { + println!("{input:#?}"); + } + return vec![to_start..to_end]; + }*/ + let mut output = Vec::new(); + let mut input = input.start..input.end; + loop { + input = + if let Some(within) = self.mapping.iter().find(|x| x.from.contains(&input.start)) { + //println!("front - {input:?} - {within:#?} - {:?}", self.from_type); + let (to_start, to_end) = if within.to.start > within.from.start { + let offset = within.to.start - within.from.start; + let end = if input.end + offset > within.to.end { + within.to.end + } else { + input.end + offset + }; + (input.start + offset, end) + } else { + let offset = within.from.start - within.to.start; + let end = if input.end - offset > within.to.end { + within.to.end + } else { + input.end - offset + }; + (input.start - offset, end) + }; + output.push(to_start..to_end); + if input.end <= within.from.end { + break; + } + within.from.end..input.end + } else if let Some(within) = self + .mapping + .iter() + .find(|x| x.from.contains(&(input.end - 1))) + { + //println!("end - {input:?} - {within:#?} - {:?}", self.from_type); + let (to_start, to_end) = if within.to.start > within.from.start { + let offset = within.to.start - within.from.start; + let start = if input.start + offset < within.to.start { + within.to.start + } else { + input.start + offset + }; + (start, input.end + offset) + } else { + let offset = within.from.start - within.to.start; + let start = if input.start + offset < within.to.start { + within.to.start + } else { + input.start + offset + }; + (start, input.end - offset) + }; + output.push(to_start..to_end); + if input.start >= within.from.start { + break; + } + input.start..within.from.start + } else { + //println!("else - {input:#?} - {:?}", self.from_type); + output.push(input.clone()); + break; + }; + } + output + } +} + +/// part2 of day 5 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +#[must_use] +pub fn part2(input: &str) -> String { + let (_input, (mut to_process, maps)) = parse_input(input).expect("aoc always has input"); + //println!("{_input}"); + let mut from_type = Type::Seed; + while from_type != Type::Location { + let current_map = maps + .iter() + .find(|x| x.from_type == from_type) + .expect("should always find"); + to_process = to_process + .iter() + .flat_map(|x| current_map.input_to_output(x)) + .unique() + .collect::>(); + //println!("{to_process:#?}"); + from_type = current_map.to_type; + } + //println!("{to_process:#?}"); + to_process + .iter() + .map(|x| x.start) + .min() + .expect("always a number") + .to_string() +} + +fn parse_item_map_entry(input: &str) -> IResult<&str, ItemMapEntry> { + let (input, to) = complete::u64(input)?; + let (input, _) = complete::space1(input)?; + let (input, from) = complete::u64(input)?; + let (input, _) = complete::space1(input)?; + let (input, count) = complete::u64(input)?; + Ok(( + input, + ItemMapEntry { + to: to..(to + count), + from: from..(from + count), + }, + )) +} + +fn parse_to_from(input: &str) -> IResult<&str, (Type, Type)> { + let (input, (to_type, from_type)) = + separated_pair(complete::alpha1, tag("-to-"), complete::alpha1)(input)?; + Ok(( + input, + ( + to_type.parse().expect("there will be a to type"), + from_type.parse().expect("there will be a from type"), + ), + )) +} + +fn parse_map(input: &str) -> IResult<&str, ItemMap> { + let (input, (from_type, to_type)) = + terminated(parse_to_from, tuple((complete::space1, tag("map:"))))(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, mapping) = separated_list1(complete::line_ending, parse_item_map_entry)(input)?; + let (input, _) = opt(complete::line_ending)(input)?; + Ok(( + input, + ItemMap { + from_type, + to_type, + mapping, + }, + )) +} + +fn parse_seed_range(input: &str) -> IResult<&str, Range> { + let (input, (seed, count)) = + separated_pair(complete::u64, complete::space1, complete::u64)(input)?; + Ok((input, seed..(seed + count))) +} + +//TODO need to change so that it operates on the ranges and not on the actual numbers + +fn parse_seeds(input: &str) -> IResult<&str, Vec>> { + let (input, _) = tag("seeds:")(input)?; + let (input, _) = complete::space1(input)?; + separated_list1(complete::space1, parse_seed_range)(input) + //println!("{seed_ranges:?}"); +} + +fn parse_input(input: &str) -> IResult<&str, (Vec>, Vec)> { + let (input, seeds) = terminated(parse_seeds, complete::line_ending)(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, maps) = separated_list1(complete::line_ending, parse_map)(input)?; + //println!("{seeds:?}"); + Ok((input, (seeds, maps))) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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 part2_works() { + let result = part2(INPUT); + assert_eq!(result, "46".to_string()); + } +} diff --git a/2023/day-6/Cargo.toml b/2023/day-6/Cargo.toml new file mode 100644 index 0000000..35431aa --- /dev/null +++ b/2023/day-6/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day-6" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true diff --git a/2023/day-6/src/lib.rs b/2023/day-6/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-6/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-6/src/main.rs b/2023/day-6/src/main.rs new file mode 100644 index 0000000..9b1d9c1 --- /dev/null +++ b/2023/day-6/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_6::part1; +use day_6::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-6/src/part1.rs b/2023/day-6/src/part1.rs new file mode 100644 index 0000000..a303413 --- /dev/null +++ b/2023/day-6/src/part1.rs @@ -0,0 +1,72 @@ +#![warn(clippy::all, clippy::pedantic)] + +use itertools::Itertools; +use nom::{ + bytes::complete::tag, + character::complete, + multi::separated_list1, + sequence::{pair, preceded}, + IResult, +}; + +/// part6 of day 1 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +#[must_use] +pub fn part1(input: &str) -> String { + let (_, races) = parse_input(input).expect("input expected"); + races + .iter() + .map(|(time, distance)| { + (0..=*time) + .filter_map(|x| { + if (time - x) * x > *distance { + Some(()) + } else { + None + } + }) + .count() + }) + .product::() + .to_string() +} + +fn parse_input(input: &str) -> IResult<&str, Vec<(u64, u64)>> { + let (input, time) = preceded( + pair(tag("Time:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, distance) = preceded( + pair(tag("Distance:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + + Ok(( + input, + time.iter() + .interleave(distance.iter()) + .copied() + .tuples() + .collect(), + )) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Time: 7 15 30 +Distance: 9 40 200"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "288".to_string()); + } +} diff --git a/2023/day-6/src/part2.rs b/2023/day-6/src/part2.rs new file mode 100644 index 0000000..2a848d6 --- /dev/null +++ b/2023/day-6/src/part2.rs @@ -0,0 +1,72 @@ +#![warn(clippy::all, clippy::pedantic)] + +use itertools::Itertools; +use nom::{ + bytes::complete::tag, + character::complete, + multi::separated_list1, + sequence::{pair, preceded}, + IResult, +}; + +/// part2 of day 2 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +#[must_use] +pub fn part2(input: &str) -> String { + let (_, race) = parse_input(input).expect("input expected"); + (0..=race.0) + .filter_map(|x| { + if (race.0 - x) * x > race.1 { + Some(()) + } else { + None + } + }) + .count() + .to_string() +} + +fn parse_input(input: &str) -> IResult<&str, (u64, u64)> { + let (input, time) = preceded( + pair(tag("Time:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, distance) = preceded( + pair(tag("Distance:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let distance = distance + .iter() + .map(ToString::to_string) + .join("") + .parse::() + .expect("is a number"); + let time = time + .iter() + .map(ToString::to_string) + .join("") + .parse::() + .expect("is a number"); + + Ok((input, (time, distance))) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Time: 7 15 30 +Distance: 9 40 200"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "71503".to_string()); + } +} diff --git a/2023/day-7/Cargo.toml b/2023/day-7/Cargo.toml new file mode 100644 index 0000000..f7b1dde --- /dev/null +++ b/2023/day-7/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "day-7" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +num = "0.4.1" +num-traits = "0.2.17" +dhat = { workspace = true } + +[features] +dhat-heap = [] diff --git a/2023/day-7/src/lib.rs b/2023/day-7/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-7/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-7/src/main.rs b/2023/day-7/src/main.rs new file mode 100644 index 0000000..49a21b9 --- /dev/null +++ b/2023/day-7/src/main.rs @@ -0,0 +1,19 @@ +#![warn(clippy::all, clippy::pedantic)] + +#[cfg(feature = "dhat-heap")] +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + +use day_7::part1; +use day_7::part2; + +fn main() { + #[cfg(feature = "dhat-heap")] + let _profiler = dhat::Profiler::new_heap(); + + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-7/src/part1.rs b/2023/day-7/src/part1.rs new file mode 100644 index 0000000..f6f4f2f --- /dev/null +++ b/2023/day-7/src/part1.rs @@ -0,0 +1,190 @@ +#![warn(clippy::all, clippy::pedantic)] +use itertools::Itertools; +use nom::{character::complete, multi::separated_list1, sequence::separated_pair, IResult}; +use std::{ + cmp::{Ord, Ordering, PartialOrd}, + collections::BTreeMap, + str::FromStr, +}; + +#[derive(Debug)] +struct Day1Part1Error; + +#[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] +enum Card { + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Jack, + Queen, + King, + Ace, +} +impl FromStr for Card { + type Err = Day1Part1Error; + fn from_str(input: &str) -> Result { + match input { + "2" => Ok(Self::Two), + "3" => Ok(Self::Three), + "4" => Ok(Self::Four), + "5" => Ok(Self::Five), + "6" => Ok(Self::Six), + "7" => Ok(Self::Seven), + "8" => Ok(Self::Eight), + "9" => Ok(Self::Nine), + "T" => Ok(Self::Ten), + "J" => Ok(Self::Jack), + "Q" => Ok(Self::Queen), + "K" => Ok(Self::King), + "A" => Ok(Self::Ace), + _ => Err(Day1Part1Error), + } + } +} + +impl From<&Card> for &u32 { + fn from(value: &Card) -> Self { + match value { + Card::Two => &2, + Card::Three => &3, + Card::Four => &4, + Card::Five => &5, + Card::Six => &6, + Card::Seven => &7, + Card::Eight => &8, + Card::Nine => &9, + Card::Ten => &10, + Card::Jack => &11, + Card::Queen => &12, + Card::King => &13, + Card::Ace => &14, + } + } +} + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +enum HandType { + HighCard, + OnePair, + TwoPair, + ThreeOfAKind, + FullHouse, + FourOfAKind, + FiveOfAKind, +} + +impl From<&Hand> for HandType { + fn from(value: &Hand) -> Self { + let map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { + if let Some(c) = acc.get_mut(card) { + *c += 1; + } else { + acc.insert(card, 1); + } + acc + }); + match map + .iter() + .sorted_by(|a, b| b.1.cmp(a.1)) + .collect::>()[..] + { + [(_, 5), ..] => Self::FiveOfAKind, + [(_, 4), ..] => Self::FourOfAKind, + [(_, 3), (_, 2), ..] => Self::FullHouse, + [(_, 3), ..] => Self::ThreeOfAKind, + [(_, 2), (_, 2), ..] => Self::TwoPair, + [(_, 2), ..] => Self::OnePair, + _ => Self::HighCard, + } + } +} + +#[derive(Debug, Eq, PartialEq)] +struct Hand { + pub cards: [Card; 5], + pub bet: u32, +} +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + let a = HandType::from(self); + let b = HandType::from(other); + let c = a.cmp(&b); + match c { + Ordering::Equal => self + .cards + .iter() + .interleave(other.cards.iter()) + .tuples::<(_, _)>() + .find_map(|(a, b)| match a.cmp(b) { + Ordering::Equal => None, + x => Some(x), + }) + .unwrap_or(Ordering::Equal), + x => x, + } + } +} + +/// part1 of day 7 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +#[must_use] +pub fn part1(input: &str) -> String { + let (_, mut hands) = parse_input(input).expect("always valid input"); + hands.sort(); + + hands + .iter() + .enumerate() + .map(|(i, hand)| (i + 1) * hand.bet as usize) + .sum::() + .to_string() +} + +fn parse_hand(input: &str) -> IResult<&str, Hand> { + let (input, (cards, bet)) = + separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; + let cards = cards + .chars() + .filter_map(|c| c.to_string().parse().ok()) + .collect::>() + .try_into() + .expect("should work"); + Ok((input, Hand { cards, bet })) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1(complete::line_ending, parse_hand)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "6440".to_string()); + } +} diff --git a/2023/day-7/src/part2.rs b/2023/day-7/src/part2.rs new file mode 100644 index 0000000..054a253 --- /dev/null +++ b/2023/day-7/src/part2.rs @@ -0,0 +1,224 @@ +#![warn(clippy::all, clippy::pedantic)] +use itertools::Itertools; +use nom::{character::complete, multi::separated_list1, sequence::separated_pair, IResult}; +use std::{ + cmp::{Ord, Ordering, PartialOrd}, + collections::BTreeMap, + str::FromStr, +}; +use std::fmt; + +#[derive(Debug)] +struct Day1Part2Error; + +#[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Copy, Clone)] +enum Card { + Joker = 1, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Queen, + King, + Ace, +} +impl FromStr for Card { + type Err = Day1Part2Error; + fn from_str(input: &str) -> Result { + match input { + "2" => Ok(Self::Two), + "3" => Ok(Self::Three), + "4" => Ok(Self::Four), + "5" => Ok(Self::Five), + "6" => Ok(Self::Six), + "7" => Ok(Self::Seven), + "8" => Ok(Self::Eight), + "9" => Ok(Self::Nine), + "T" => Ok(Self::Ten), + "J" => Ok(Self::Joker), + "Q" => Ok(Self::Queen), + "K" => Ok(Self::King), + "A" => Ok(Self::Ace), + _ => Err(Day1Part2Error), + } + } +} + +impl From<&Card> for &u32 { + fn from(value: &Card) -> Self { + match value { + Card::Two => &2, + Card::Three => &3, + Card::Four => &4, + Card::Five => &5, + Card::Six => &6, + Card::Seven => &7, + Card::Eight => &8, + Card::Nine => &9, + Card::Ten => &10, + Card::Joker => &1, + Card::Queen => &12, + Card::King => &13, + Card::Ace => &14, + } + } +} +impl fmt::Display for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + let c = match self { + Card::Joker => 'J', + Card::Two => '2', + Card::Three => '3', + Card::Four => '4', + Card::Five => '5', + Card::Six => '6', + Card::Seven => '7', + Card::Eight => '8', + Card::Nine => '9', + Card::Ten => 'T', + Card::Queen => 'Q', + Card::King => 'K', + Card::Ace => 'A', + }; + write!(f, "{c}") + } +} + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +enum HandType { + HighCard, + OnePair, + TwoPair, + ThreeOfAKind, + FullHouse, + FourOfAKind, + FiveOfAKind, +} + +impl From<&Hand> for HandType { + fn from(value: &Hand) -> Self { + let mut map = value.cards.iter().fold(BTreeMap::new(), |mut acc, card| { + if let Some(c) = acc.get_mut(card) { + *c += 1; + } else { + acc.insert(card, 1); + } + acc + }); + let jokers = map.remove(&Card::Joker).unwrap_or(0); + match map + .iter() + .sorted_by(|a, b| b.1.cmp(a.1)) + .collect::>()[..] + { + [(_, x), ..] if jokers + x == 5 => Self::FiveOfAKind, + [] if jokers == 5 => Self::FiveOfAKind, + [(_, x), ..] if jokers + x == 4 => Self::FourOfAKind, + [(_, 3), (_, 2)] => Self::FullHouse, + [(_, 2), (_, 2)] if jokers == 1 => Self::FullHouse, + [(_, x), ..] if jokers + x == 3 => Self::ThreeOfAKind, + [(_, 2), (_, 2), ..] => Self::TwoPair, + [(_, x), ..] if jokers + x == 2 => Self::OnePair, + _ => Self::HighCard, + } + } +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +struct Hand { + pub cards: [Card; 5], + pub bet: u32, +} +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + let a = HandType::from(self); + let b = HandType::from(other); + let c = a.cmp(&b); + match c { + Ordering::Equal => self + .cards + .iter() + .interleave(other.cards.iter()) + .tuples::<(_, _)>() + .find_map(|(a, b)| match a.cmp(b) { + Ordering::Equal => None, + x => Some(x), + }) + .unwrap_or(Ordering::Equal), + x => x, + } + } +} + +/// part2 of day 7 of AOC 2023 +/// +/// # Arguments +/// - input the puszzle input +/// +/// # Panics +/// panics whenever the input isn't parsable +#[must_use] +pub fn part2(input: &str) -> String { + let (_, mut hands) = parse_input(input).expect("always valid input"); + hands.sort(); + + hands + .iter() + .enumerate() + .map(|(i, hand)| (i + 1) * hand.bet as usize) + .sum::() + .to_string() +} + +fn parse_hand(input: &str) -> IResult<&str, Hand> { + let (input, (cards, bet)) = + separated_pair(complete::alphanumeric1, complete::space1, complete::u32)(input)?; + let cards = cards + .chars() + .filter_map(|c| c.to_string().parse().ok()) + .collect::>() + .try_into() + .expect("should work"); + Ok((input, Hand { cards, bet })) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1(complete::line_ending, parse_hand)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn qtest() { + let a_str = "JKKK2 9"; + let b_str = "QQQQ2 8"; + let (_, a_hand) = parse_hand(a_str).expect("shoould parse a"); + let (_, b_hand) = parse_hand(b_str).expect("should parse b"); + let c = a_hand.cmp(&b_hand); + assert_eq!(c, Ordering::Less); + } + + const INPUT: &str = "32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "5905".to_string()); + } +} diff --git a/2023/day-8/Cargo.toml b/2023/day-8/Cargo.toml new file mode 100644 index 0000000..c4544a9 --- /dev/null +++ b/2023/day-8/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-8" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +rstest = {workspace = true} diff --git a/2023/day-8/src/lib.rs b/2023/day-8/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-8/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-8/src/main.rs b/2023/day-8/src/main.rs new file mode 100644 index 0000000..8fb6f62 --- /dev/null +++ b/2023/day-8/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_8::part1; +use day_8::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-8/src/part1.rs b/2023/day-8/src/part1.rs new file mode 100644 index 0000000..533fe9b --- /dev/null +++ b/2023/day-8/src/part1.rs @@ -0,0 +1,134 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + multi::{many1, separated_list1}, + sequence::{delimited, pair, separated_pair, tuple}, + IResult, Parser, +}; +use std::collections::BTreeMap; + +#[derive(Debug, Copy, Clone)] +enum Direction { + Left, + Right, +} + +#[derive(Debug, Clone)] +struct Branches { + pub left: String, + pub right: String, +} + +impl Branches { + fn choose(&self, direction: Direction) -> &str { + match direction { + Direction::Left => &self.left, + Direction::Right => &self.right, + } + } +} + +/// day 8 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part1(input: &str) -> String { + let (_, (steps, branches)) = parse_input(input).expect("aoc expects valid input"); + + let mut current = "AAA"; + let mut count = 0_usize; + for x in steps.iter().cycle() { + if current == "ZZZ" { + break; + } + current = branches.get(current).expect("aoc").choose(*x); + count += 1; + } + count.to_string() +} + +fn parse_directions(input: &str) -> IResult<&str, Vec> { + let (input, directions) = many1(alt(( + tag("L").map(|_| Direction::Left), + tag("R").map(|_| Direction::Right), + )))(input)?; + let (input, _) = complete::line_ending(input)?; + Ok((input, directions)) +} + +fn parse_branches(input: &str) -> IResult<&str, Branches> { + let (input, (left, right)) = delimited( + pair(tag("("), complete::space0), + separated_pair( + complete::alpha1, + pair(tag(","), complete::space1), + complete::alpha1, + ), + pair(complete::space0, tag(")")), + )(input)?; + let left = left.to_string(); + let right = right.to_string(); + Ok((input, Branches { left, right })) +} + +fn parse_nodes(input: &str) -> IResult<&str, (String, Branches)> { + let (input, (node, branches)) = separated_pair( + complete::alpha1, + tuple((complete::space1, tag("="), complete::space1)), + parse_branches, + )(input)?; + Ok((input, (node.to_string(), branches))) +} + +fn parse_node_tree(input: &str) -> IResult<&str, BTreeMap> { + let (input, map) = separated_list1(complete::line_ending, parse_nodes)(input)?; + let map = map.into_iter().collect(); + Ok((input, map)) +} + +fn parse_input(input: &str) -> IResult<&str, (Vec, BTreeMap)> { + let (input, x) = + separated_pair(parse_directions, complete::line_ending, parse_node_tree)(input)?; + Ok((input, x)) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + "RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ)", + "2" + )] + #[case( + "LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ)", + "6" + )] + + fn part1_works(#[case] input: &str, #[case] expected: &str) { + let result = part1(input); + assert_eq!(result, expected); + } +} diff --git a/2023/day-8/src/part2.rs b/2023/day-8/src/part2.rs new file mode 100644 index 0000000..48ea029 --- /dev/null +++ b/2023/day-8/src/part2.rs @@ -0,0 +1,160 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete, + multi::{many1, separated_list1}, + sequence::{delimited, pair, separated_pair, tuple}, + IResult, Parser, +}; +use std::collections::BTreeMap; + +#[derive(Debug, Copy, Clone)] +enum Direction { + Left, + Right, +} + +struct Branches { + pub left: String, + pub right: String, +} + +impl Branches { + fn choose(&self, direction: Direction) -> &str { + match direction { + Direction::Left => &self.left, + Direction::Right => &self.right, + } + } +} + +/// day 8 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str) -> String { + let (_, (steps, branches)) = parse_input(input).expect("aoc expects valid input"); + + let starting_node: Vec<&str> = branches + .keys() + .map(String::as_str) + .filter(|x| x.ends_with('A')) + .collect(); + + let cycles = starting_node + .iter() + .map(|node| { + let mut visited_nodes = vec![*node]; + let mut current = *node; + steps + .iter() + .cycle() + .enumerate() + .find_map(|(i, direction)| { + let new = branches.get(current).expect("aoc1").choose(*direction); + if new.ends_with('Z') { + return Some(i + 1); + } + visited_nodes.push(new); + dbg!(current = new); + None + }) + .expect("aoc4") + }) + .collect::>(); + lcm(&cycles).to_string() +} + +fn lcm(nums: &[usize]) -> usize { + if nums.len() == 1 { + return nums[0]; + } + let a = nums[0]; + let b = lcm(&nums[1..]); + a * b / gcd(a, b) +} + +fn gcd(a: usize, b: usize) -> usize { + if b == 0 { + return a; + } + gcd(b, a % b) +} + +fn parse_directions(input: &str) -> IResult<&str, Vec> { + let (input, directions) = many1(alt(( + tag("L").map(|_| Direction::Left), + tag("R").map(|_| Direction::Right), + )))(input)?; + let (input, _) = complete::line_ending(input)?; + Ok((input, directions)) +} + +fn parse_branches(input: &str) -> IResult<&str, Branches> { + let (input, (left, right)) = delimited( + pair(tag("("), complete::space0), + separated_pair( + complete::alphanumeric1, + pair(tag(","), complete::space1), + complete::alphanumeric1, + ), + pair(complete::space0, tag(")")), + )(input)?; + let left = left.to_string(); + let right = right.to_string(); + Ok((input, Branches { left, right })) +} + +fn parse_nodes(input: &str) -> IResult<&str, (String, Branches)> { + let (input, (node, branches)) = separated_pair( + complete::alphanumeric1, + tuple((complete::space1, tag("="), complete::space1)), + parse_branches, + )(input)?; + Ok((input, (node.to_string(), branches))) +} + +fn parse_node_tree(input: &str) -> IResult<&str, BTreeMap> { + let (input, map) = separated_list1(complete::line_ending, parse_nodes)(input)?; + let map = map.into_iter().collect(); + Ok((input, map)) +} + +fn parse_input(input: &str) -> IResult<&str, (Vec, BTreeMap)> { + let (input, x) = + separated_pair(parse_directions, complete::line_ending, parse_node_tree)(input)?; + Ok((input, x)) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + "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)", + "6" + )] + + fn part2_works(#[case] input: &str, #[case] expected: &str) { + let result = part2(input); + assert_eq!(result, expected); + } +} diff --git a/2023/day-9/Cargo.toml b/2023/day-9/Cargo.toml new file mode 100644 index 0000000..bf88608 --- /dev/null +++ b/2023/day-9/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-9" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true +rstest = {workspace = true} diff --git a/2023/day-9/src/lib.rs b/2023/day-9/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-9/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-9/src/main.rs b/2023/day-9/src/main.rs new file mode 100644 index 0000000..f361f54 --- /dev/null +++ b/2023/day-9/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_9::part1; +use day_9::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-9/src/part1.rs b/2023/day-9/src/part1.rs new file mode 100644 index 0000000..9cb03ad --- /dev/null +++ b/2023/day-9/src/part1.rs @@ -0,0 +1,62 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{character::complete, multi::separated_list1, IResult}; +use std::{iter::successors, ops::Not}; + +/// day 9 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part1(input: &str) -> String { + let (_, report) = parse_input(input).expect("should have valid input for aoc"); + report.iter().map(|x| get_next(x)).sum::().to_string() +} + +fn get_next(array: &[i64]) -> i64 { + let array = Vec::from(array); + successors(Some(array), |a| { + a.iter() + .all(|x| x == &0) + .not() + .then_some(a.windows(2).map(|a| a[1] - a[0]).collect::>()) + }) + .map(|x| *x.last().unwrap()) + .sum() +} + +fn parse_input(input: &str) -> IResult<&str, Vec>> { + separated_list1( + complete::line_ending, + separated_list1(complete::space1, complete::i64), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case(vec![0,3,6,9,12,15], 18)] + #[case(vec![1,3,6,10,15,21], 28)] + #[case(vec![10,13,16,21,30,45], 68)] + fn part1_next(#[case] array: Vec, #[case] expected: i64) { + assert_eq!(get_next(&array), expected); + } + + const INPUT: &str = "0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "114".to_string()); + } +} + diff --git a/2023/day-9/src/part2.rs b/2023/day-9/src/part2.rs new file mode 100644 index 0000000..bcb4c33 --- /dev/null +++ b/2023/day-9/src/part2.rs @@ -0,0 +1,64 @@ +#![warn(clippy::all, clippy::pedantic)] + +use nom::{character::complete, multi::separated_list1, IResult}; +use std::{iter::successors, ops::Not}; + +/// day 9 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str) -> String { + let (_, report) = parse_input(input).expect("should have valid input for aoc"); + report.iter().map(|x| get_next(x)).sum::().to_string() +} + +fn get_next(array: &[i64]) -> i64 { + let array = Vec::from(array); + let mut a = successors(Some(array.clone()), |a| { + a.iter() + .all(|x| x == &0) + .not() + .then_some(a.windows(2).map(|a| a[1] - a[0]).collect::>()) + }) + .map(|x| *x.first().unwrap()) + .collect::>(); + a.reverse(); + a.iter().fold(0, |acc, x| x - acc) +} + +fn parse_input(input: &str) -> IResult<&str, Vec>> { + separated_list1( + complete::line_ending, + separated_list1(complete::space1, complete::i64), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + + #[rstest] + #[case(vec![0,3,6,9,12,15], -3)] + #[case(vec![1,3,6,10,15,21], 0)] + #[case(vec![10,13,16,21,30,45], 5)] + fn part2_next(#[case] array: Vec, #[case] expected: i64) { + assert_eq!(get_next(&array), expected); + } + + const INPUT: &str = "0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "2".to_string()); + } +} + diff --git a/template/day-n/Cargo.toml b/template/day-n/Cargo.toml new file mode 100644 index 0000000..20c5d95 --- /dev/null +++ b/template/day-n/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "{{project-name}}" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = { workspace = true } +itertools = {workspace = true } diff --git a/template/day-n/src/input.txt b/template/day-n/src/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/template/day-n/src/lib.rs b/template/day-n/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/template/day-n/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/template/day-n/src/main.rs b/template/day-n/src/main.rs new file mode 100644 index 0000000..07ab7f3 --- /dev/null +++ b/template/day-n/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use {{crate_name}}::part1; +use {{crate_name}}::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/template/day-n/src/part1.rs b/template/day-n/src/part1.rs new file mode 100644 index 0000000..a7db908 --- /dev/null +++ b/template/day-n/src/part1.rs @@ -0,0 +1,20 @@ +#![warn(clippy::all, clippy::pedantic)] + +#[must_use] +pub fn part1 (_input: &str) -> String { + "Not Finished".to_string() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = ""; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "Not Finished".to_string()); + } +} + diff --git a/template/day-n/src/part2.rs b/template/day-n/src/part2.rs new file mode 100644 index 0000000..8f15571 --- /dev/null +++ b/template/day-n/src/part2.rs @@ -0,0 +1,20 @@ +#![warn(clippy::all, clippy::pedantic)] + +#[must_use] +pub fn part2 (_input: &str) -> String { + "Not Finished".to_string() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = ""; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "Not Finished".to_string()); + } +} +