diff --git a/Cargo.lock b/Cargo.lock index 937729d..aa64cc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,75 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" name = "advent-of-code" version = "0.0.0" dependencies = [ + "askama", + "clap", "color-eyre", + "derivative", + "dialoguer", + "emojis", "itertools", "pathfinding", + "rand", + "ureq", +] + +[[package]] +name = "askama" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", +] + +[[package]] +name = "askama_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +dependencies = [ + "askama_shared", + "proc-macro2", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_shared" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" +dependencies = [ + "askama_escape", + "humansize", + "mime", + "mime_guess", + "nom", + "num-traits", + "percent-encoding", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", ] [[package]] @@ -47,6 +113,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + [[package]] name = "cc" version = "1.0.73" @@ -59,6 +143,49 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clap" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78ad8e84aa8e8aa3e821857be40eb4b925ff232de430d4dd2ae6aa058cbd92" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "color-eyre" version = "0.6.2" @@ -86,12 +213,97 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "unicode-width", + "winapi", +] + +[[package]] +name = "cookie" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +dependencies = [ + "cookie", + "idna 0.2.3", + "indexmap", + "log", + "publicsuffix", + "serde", + "serde_json", + "time", + "url", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dialoguer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" +dependencies = [ + "console", + "tempfile", + "zeroize", +] + [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "emojis" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdc0036e5881a30eec779c55ac7b4bf54eedc3c72ddc8d02c2a580c43b1c9ba" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "eyre" version = "0.6.8" @@ -102,12 +314,51 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.26.2" @@ -120,6 +371,48 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.3" @@ -136,6 +429,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -154,6 +456,21 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -166,12 +483,49 @@ version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[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.5.4" @@ -181,6 +535,16 @@ dependencies = [ "adler", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -190,6 +554,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.29.0" @@ -205,6 +578,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + [[package]] name = "owo-colors" version = "3.5.0" @@ -226,12 +605,48 @@ dependencies = [ "thiserror", ] +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.46" @@ -241,6 +656,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeeedb0b429dc462f30ad27ef3de97058b060016f47790c066757be38ef792b4" +dependencies = [ + "idna 0.2.3", + "psl-types", +] + [[package]] name = "quote" version = "1.0.21" @@ -250,6 +681,69 @@ 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.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -262,6 +756,65 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -271,6 +824,18 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.101" @@ -282,6 +847,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.37" @@ -311,6 +909,48 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.36" @@ -353,14 +993,212 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +dependencies = [ + "base64", + "chunked_transfer", + "cookie", + "cookie_store", + "flate2", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna 0.3.0", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml index 8695873..47d5c6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,13 @@ name = "advent-of-code" path = "source/main.rs" [dependencies] +askama = "0.11.1" +clap = { version = "4.0.4", features = ["derive"] } color-eyre = "0.6.2" +dialoguer = "0.10.2" +derivative = "2.2.0" +emojis = "0.4.0" itertools = "0.10.5" pathfinding = "3.0.13" +rand = "0.8.5" +ureq = { version = "2.5.0", features = ["cookie", "cookie_store"] } diff --git a/askama.toml b/askama.toml new file mode 100644 index 0000000..fcc0ab4 --- /dev/null +++ b/askama.toml @@ -0,0 +1,2 @@ +[general] +dirs = ["source/templates"] diff --git a/source/day_04/mod.rs b/source/day_04/mod.rs deleted file mode 100644 index 734330e..0000000 --- a/source/day_04/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::str::FromStr; - -use color_eyre::Result; - -mod bingo; -mod board; - -use bingo::Bingo; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_04.txt").trim(); - println!("Day 04 Part 1: {}", part_1(input_data)?); - println!("Day 04 Part 2: {}", part_2(input_data)?); - Ok(()) -} - -fn part_1(input: &str) -> Result { - let (winning_board, latest_number) = - Bingo::from_str(input)?.play_until_first_win(); - Ok(winning_board.sum_unmarked() * latest_number) -} - -fn part_2(input: &str) -> Result { - let (winning_board, latest_number) = - Bingo::from_str(input)?.play_until_last_win(); - Ok(winning_board.sum_unmarked() * latest_number) -} diff --git a/source/main.rs b/source/main.rs index 57f5cff..20d559c 100644 --- a/source/main.rs +++ b/source/main.rs @@ -1,54 +1,73 @@ -use std::time::{Duration, Instant}; +//! # Advent of Code +//! +//! > **Advent of Code solutions in Rust.** -use color_eyre::Result; +#![forbid(unsafe_code)] -mod day_01; -mod day_02; -mod day_03; -mod day_04; -mod day_05; -mod day_06; -mod day_07; -mod day_08; -mod day_09; -mod day_10; -mod day_13; -mod day_14; -mod day_15; +use std::{fs::read_to_string, path::PathBuf}; -fn main() -> Result<()> { +use {askama::Template, clap::Parser, color_eyre::Result}; + +use crate::utilities::{get_solutions, session_cookie, write_file}; + +pub mod prelude; +pub mod solution; +pub mod templates; +pub mod utilities; +pub mod year_2021; + +#[derive(Debug, Parser)] +#[clap(about, author, version)] +pub struct Args { + /// Filter to only a specific year and/or day, in `YEAR::DAY` format. + pub filter: Option, +} + +pub fn main() -> Result<()> { color_eyre::install()?; - println!("Advent of Code 2021\n"); - let mut runtimes = vec![]; - let days: Vec Result<()>> = vec![ - day_01::solve, - day_02::solve, - day_03::solve, - day_04::solve, - day_05::solve, - day_06::solve, - day_07::solve, - day_08::solve, - day_09::solve, - day_10::solve, - day_13::solve, - day_14::solve, - day_15::solve, - ]; + let args = Args::parse(); + let filter = args.filter.unwrap_or_default(); + let session_cookie = session_cookie()?; + let mut years = vec![]; - for day in days { - let start = Instant::now(); - day()?; - let runtime = Instant::now() - start; - runtimes.push(runtime); - println!("- Runtime: {:#?}\n", runtime); + for year in get_solutions() { + let mut solutions = vec![]; + + for solution in year { + if !solution.day.filter_string().starts_with(&filter) { + continue; + } + + let year = solution.day.year; + let day = solution.day.day; + + let data_path = PathBuf::from(format!("aoc/{year}/{day:02}.txt")); + let data = if !data_path.exists() { + println!("{data_path:?} doesn't exist, downloading"); + std::thread::sleep(std::time::Duration::from_secs(1)); + write_file( + data_path, + ureq::get(&format!( + "https://adventofcode.com/{year}/day/{day}/input" + )) + .set("Cookie", &session_cookie) + .call()? + .into_string()?, + )? + } else { + read_to_string(data_path)? + }; + + solutions.push(solution.solve(data.trim())?); + } + + years.append(&mut vec![solutions]); } - println!( - "Total runtime: {:#?}", - runtimes.into_iter().sum::() - ); - + write_file( + "aoc/solutions.html".into(), + templates::SolutionsTemplate { years }.render()?, + )?; Ok(()) } diff --git a/source/prelude.rs b/source/prelude.rs new file mode 100644 index 0000000..3e0b031 --- /dev/null +++ b/source/prelude.rs @@ -0,0 +1,17 @@ +//! Commonly used imports. + +pub use std::{ + collections::{HashMap, HashSet, VecDeque}, + str::FromStr, +}; + +pub use { + color_eyre::{ + eyre::{eyre, Error}, + Result, + }, + itertools::Itertools, + pathfinding::prelude::astar, +}; + +pub use crate::solution::{Day, Solution}; diff --git a/source/solution.rs b/source/solution.rs new file mode 100644 index 0000000..fbd1bd4 --- /dev/null +++ b/source/solution.rs @@ -0,0 +1,83 @@ +use {color_eyre::Result, derivative::Derivative}; + +#[derive(Debug)] +pub struct Day { + pub day: i32, + + pub year: i32, +} + +impl Day { + pub fn new(day: i32, year: i32) -> Self { + Self { day, year } + } + + pub fn filter_string(&self) -> String { + format!("{}::{:02}", self.year, self.day) + } + + pub fn day_link(&self) -> String { + format!("{}/day/{}", self.year_link(), self.day) + } + + pub fn year_link(&self) -> String { + format!("https://adventofcode.com/{}", self.year) + } +} + +pub type DayFunction = fn(input: &str) -> Result; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Solution { + pub day: Day, + + pub part_1: String, + + pub part_2: String, + + pub part_1_expected: String, + + pub part_2_expected: String, + + #[derivative(Debug = "ignore")] + pub part_1_fn: DayFunction, + + #[derivative(Debug = "ignore")] + pub part_2_fn: DayFunction, +} + +impl Solution { + pub fn new(day: Day, part_1_fn: DayFunction, part_2_fn: DayFunction) -> Self { + Self { + day, + part_1: String::new(), + part_2: String::new(), + part_1_expected: String::new(), + part_2_expected: String::new(), + part_1_fn, + part_2_fn, + } + } + + pub fn with_expected( + self, + part_1_expected: A, + part_2_expected: B, + ) -> Self { + Self { + part_1_expected: format!("{part_1_expected}"), + part_2_expected: format!("{part_2_expected}"), + ..self + } + } + + pub fn solve(self, data: &str) -> Result { + let (part_1, part_2) = ((self.part_1_fn)(data)?, (self.part_2_fn)(data)?); + Ok(Self { + part_1, + part_2, + ..self + }) + } +} diff --git a/source/templates/mod.rs b/source/templates/mod.rs new file mode 100644 index 0000000..b3cc8c5 --- /dev/null +++ b/source/templates/mod.rs @@ -0,0 +1,15 @@ +use askama::Template; + +use crate::solution::Solution; + +#[derive(Template)] +#[template(path = "solutions.html")] +pub struct SolutionsTemplate { + pub years: Vec>, +} + +pub mod filters { + pub fn random_emoji(s: &str) -> askama::Result { + Ok(format!("{s} {}", crate::utilities::random_emoji())) + } +} diff --git a/source/templates/solutions.html b/source/templates/solutions.html new file mode 100644 index 0000000..f6ba957 --- /dev/null +++ b/source/templates/solutions.html @@ -0,0 +1,156 @@ + + + + + + + + Advent of Code Solutions + + + + + + + +
+ {% for year in years %} +
+ {% for solution in year %} + {% if loop.first %} +

+ {{ solution.day.year }} +

+ {% endif %} + +
+

+ + Day {{ format!("{:02}", solution.day.day)|random_emoji }} + +

+
+ {% let part_1_correct %} + {% if solution.part_1 == solution.part_1_expected %} + {% let part_1_correct = true %} + {% else %} + {% let part_1_correct = false %} + {% endif %} + + {% if solution.part_1.contains("\n") %} +
+ Click to expand. +
{{ solution.part_1 }}
+
+ {% else %} +
{{ solution.part_1 }}
+ {% endif %} + + {% let part_2_correct %} + {% if solution.part_2 == solution.part_2_expected %} + {% let part_2_correct = true %} + {% else %} + {% let part_2_correct = false %} + {% endif %} + + {% if solution.part_2.contains("\n") %} +
+ Click to expand. +
{{ solution.part_2 }}
+
+ {% else %} +
{{ solution.part_2 }}
+ {% endif %} +
+
+ {% endfor %} +
+ {% endfor %} +
+ + + diff --git a/source/utilities.rs b/source/utilities.rs new file mode 100644 index 0000000..954f411 --- /dev/null +++ b/source/utilities.rs @@ -0,0 +1,40 @@ +use std::{ + fs::{create_dir_all, read_to_string, write}, + path::PathBuf, +}; + +use {color_eyre::Result, dialoguer::Password, rand::seq::IteratorRandom}; + +use crate::{solution::Solution, year_2021}; + +pub fn get_solutions() -> Vec> { + vec![year_2021::get_solutions()] +} + +pub fn random_emoji() -> String { + emojis::iter() + .filter(|emoji| emoji.group() == emojis::Group::AnimalsAndNature) + .choose(&mut rand::thread_rng()) + .unwrap() + .to_string() +} + +pub fn session_cookie() -> Result { + let session_cookie_path = PathBuf::from("aoc/session.txt"); + + if session_cookie_path.exists() { + read_to_string(session_cookie_path).map_err(Into::into) + } else { + let session_cookie = Password::new() + .with_prompt("Advent of Code Session Cookie") + .interact()?; + + write_file(session_cookie_path, session_cookie) + } +} + +pub fn write_file(path: PathBuf, contents: String) -> Result { + create_dir_all(path.parent().unwrap())?; + write(path, &contents)?; + Ok(contents) +} diff --git a/source/day_01/mod.rs b/source/year_2021/day_01/mod.rs similarity index 54% rename from source/day_01/mod.rs rename to source/year_2021/day_01/mod.rs index 6048b8a..49feabb 100644 --- a/source/day_01/mod.rs +++ b/source/year_2021/day_01/mod.rs @@ -1,30 +1,28 @@ -use color_eyre::Result; -use itertools::Itertools; +use crate::prelude::*; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_01.txt").trim(); - println!("Day 01 Part 1: {}", part_1(input_data)?); - println!("Day 01 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(1, 2021), part_1, part_2).with_expected(1451, 1395) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { Ok( parse_measurements(input)? .into_iter() .tuple_windows() .filter(|(previous, next)| next > previous) - .count(), + .count() + .to_string(), ) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { Ok( parse_measurements(input)? .into_iter() .tuple_windows() .filter(|(a, b, c, d)| b + c + d > a + b + c) - .count(), + .count() + .to_string(), ) } diff --git a/source/day_02/mod.rs b/source/year_2021/day_02/mod.rs similarity index 74% rename from source/day_02/mod.rs rename to source/year_2021/day_02/mod.rs index f679c31..440a6d2 100644 --- a/source/day_02/mod.rs +++ b/source/year_2021/day_02/mod.rs @@ -1,15 +1,8 @@ -use std::str::FromStr; +use crate::prelude::*; -use color_eyre::{ - eyre::{eyre, Error}, - Result, -}; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_02.txt").trim(); - println!("Day 02 Part 1: {}", part_1(input_data)?); - println!("Day 02 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(2, 2021), part_1, part_2) + .with_expected(1727835, 1544000595) } #[derive(Debug)] @@ -23,13 +16,13 @@ impl FromStr for Command { type Err = Error; fn from_str(s: &str) -> Result { - let mut split = s.split(" "); + let mut split = s.split(' '); let command = split .next() - .ok_or(eyre!("Command not found in line: {}", s))?; + .ok_or_else(|| eyre!("Command not found in line: {}", s))?; let amount = split .next() - .ok_or(eyre!("Amount not found in line: {}", s))? + .ok_or_else(|| eyre!("Amount not found in line: {}", s))? .parse()?; match command { @@ -77,22 +70,22 @@ fn parse_commands(input: &str) -> Result> { input.lines().map(Command::from_str).collect::>() } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let mut submarine = Submarine::default(); parse_commands(input)? .into_iter() .for_each(|command| submarine.execute_command_1(command)); - Ok(submarine.final_result()) + Ok(submarine.final_result().to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let mut submarine = Submarine::default(); parse_commands(input)? .into_iter() .for_each(|command| submarine.execute_command_2(command)); - Ok(submarine.final_result()) + Ok(submarine.final_result().to_string()) } diff --git a/source/day_03/mod.rs b/source/year_2021/day_03/mod.rs similarity index 75% rename from source/day_03/mod.rs rename to source/year_2021/day_03/mod.rs index 3363efa..67018cc 100644 --- a/source/day_03/mod.rs +++ b/source/year_2021/day_03/mod.rs @@ -1,13 +1,8 @@ -use std::collections::HashMap; +use crate::prelude::*; -use color_eyre::{eyre::eyre, Result}; -use itertools::Itertools; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_03.txt").trim(); - println!("Day 03 Part 1: {}", part_1(input_data)?); - println!("Day 03 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(3, 2021), part_1, part_2) + .with_expected(1092896, 4672151) } fn count_bits(input: &str) -> Result> { @@ -33,7 +28,7 @@ fn count_bits(input: &str) -> Result> { ) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let bits = count_bits(input)?; let gamma_rate = i32::from_str_radix( @@ -53,10 +48,10 @@ fn part_1(input: &str) -> Result { 2, )?; - Ok(gamma_rate * epsilon_rate) + Ok((gamma_rate * epsilon_rate).to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let mut most_common_lines = input.lines().collect::>(); let mut least_common_lines = input.lines().collect::>(); @@ -67,7 +62,7 @@ fn part_2(input: &str) -> Result { if most_common_lines.len() > 1 { let (index, bit) = most_common_bits .get(index) - .ok_or(eyre!("Could not find most common bit"))?; + .ok_or_else(|| eyre!("Could not find most common bit"))?; let most_common = if bit >= &0 { '1' } else { '0' }; most_common_lines = most_common_lines .into_iter() @@ -79,7 +74,7 @@ fn part_2(input: &str) -> Result { if least_common_lines.len() > 1 { let (index, bit) = least_common_bits .get(index) - .ok_or(eyre!("Could not find least common bit"))?; + .ok_or_else(|| eyre!("Could not find least common bit"))?; let least_common = if bit < &0 { '1' } else { '0' }; least_common_lines = least_common_lines .into_iter() @@ -92,15 +87,15 @@ fn part_2(input: &str) -> Result { let oxygen_generator_rating = i32::from_str_radix( most_common_lines .first() - .ok_or(eyre!("Didn't find an oxygen generator rating"))?, + .ok_or_else(|| eyre!("Didn't find an oxygen generator rating"))?, 2, )?; let co2_scrubber_rating = i32::from_str_radix( least_common_lines .first() - .ok_or(eyre!("Didn't find a CO2 scrubber rating"))?, + .ok_or_else(|| eyre!("Didn't find a CO2 scrubber rating"))?, 2, )?; - Ok(oxygen_generator_rating * co2_scrubber_rating) + Ok((oxygen_generator_rating * co2_scrubber_rating).to_string()) } diff --git a/source/day_04/bingo.rs b/source/year_2021/day_04/bingo.rs similarity index 93% rename from source/day_04/bingo.rs rename to source/year_2021/day_04/bingo.rs index abc19a1..03fddd2 100644 --- a/source/day_04/bingo.rs +++ b/source/year_2021/day_04/bingo.rs @@ -59,10 +59,10 @@ impl FromStr for Bingo { fn from_str(s: &str) -> Result { let numbers = s - .split("\n") + .split('\n') .next() - .ok_or(eyre!("Didn't find bingo numbers on the first line"))? - .split(",") + .ok_or_else(|| eyre!("Didn't find bingo numbers on the first line"))? + .split(',') .map(str::parse) .collect::>()?; diff --git a/source/day_04/board.rs b/source/year_2021/day_04/board.rs similarity index 86% rename from source/day_04/board.rs rename to source/year_2021/day_04/board.rs index c52a1da..4a507e1 100644 --- a/source/day_04/board.rs +++ b/source/year_2021/day_04/board.rs @@ -5,17 +5,17 @@ use color_eyre::eyre::Error; /// A matrix of all the indexes that constitute a win. const WIN_MATRIX: &[&[usize]] = &[ // Horizontal lines - &[00, 01, 02, 03, 04], - &[05, 06, 07, 08, 09], + &[0, 1, 2, 3, 4], + &[5, 6, 7, 8, 9], &[10, 11, 12, 13, 14], &[15, 16, 17, 18, 19], &[20, 21, 22, 23, 24], // Vertical lines - &[00, 05, 10, 15, 20], - &[01, 06, 11, 16, 21], - &[02, 07, 12, 17, 22], - &[03, 08, 13, 18, 23], - &[04, 09, 14, 19, 24], + &[0, 5, 10, 15, 20], + &[1, 6, 11, 16, 21], + &[2, 7, 12, 17, 22], + &[3, 8, 13, 18, 23], + &[4, 9, 14, 19, 24], ]; #[derive(Debug, Clone)] @@ -36,7 +36,7 @@ impl Board { /// Check whether a board has won. pub fn check(&self) -> bool { for indexes in WIN_MATRIX { - if indexes.into_iter().all(|index| { + if indexes.iter().all(|index| { *self .state .get(*index) @@ -78,8 +78,8 @@ impl FromStr for Board { fn from_str(s: &str) -> Result { let numbers = s - .replace("\n", " ") - .split(" ") + .replace('\n', " ") + .split(' ') .filter(|s| s != &"") .map(str::parse) .collect::>()?; diff --git a/source/year_2021/day_04/mod.rs b/source/year_2021/day_04/mod.rs new file mode 100644 index 0000000..97ffebe --- /dev/null +++ b/source/year_2021/day_04/mod.rs @@ -0,0 +1,22 @@ +mod bingo; +mod board; + +use bingo::Bingo; + +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(4, 2021), part_1, part_2).with_expected(8580, 9576) +} + +fn part_1(input: &str) -> Result { + let (winning_board, latest_number) = + Bingo::from_str(input)?.play_until_first_win(); + Ok((winning_board.sum_unmarked() * latest_number).to_string()) +} + +fn part_2(input: &str) -> Result { + let (winning_board, latest_number) = + Bingo::from_str(input)?.play_until_last_win(); + Ok((winning_board.sum_unmarked() * latest_number).to_string()) +} diff --git a/source/day_05/mod.rs b/source/year_2021/day_05/mod.rs similarity index 86% rename from source/day_05/mod.rs rename to source/year_2021/day_05/mod.rs index 1630189..1526769 100644 --- a/source/day_05/mod.rs +++ b/source/year_2021/day_05/mod.rs @@ -1,15 +1,7 @@ -use std::{collections::HashMap, str::FromStr}; +use crate::prelude::*; -use color_eyre::{ - eyre::{eyre, Error}, - Result, -}; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_05.txt").trim(); - println!("Day 05 Part 1: {}", part_1(input_data)?); - println!("Day 05 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(5, 2021), part_1, part_2).with_expected(6687, 19851) } #[derive(Debug)] @@ -84,7 +76,7 @@ impl FromStr for Vector { fn from_str(s: &str) -> Result { let numbers = s .replace(" -> ", ",") - .split(",") + .split(',') .map(str::parse) .collect::, _>>()?; @@ -99,7 +91,7 @@ impl FromStr for Vector { } } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let vectors = input .lines() .filter_map(|line| { @@ -127,10 +119,10 @@ fn part_1(input: &str) -> Result { } } - Ok(count_result(travelled_coordinates)) + Ok(count_result(travelled_coordinates).to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let vectors = input .lines() .map(str::parse) @@ -152,7 +144,7 @@ fn part_2(input: &str) -> Result { } } - Ok(count_result(travelled_coordinates)) + Ok(count_result(travelled_coordinates).to_string()) } fn count_result(coordinates: HashMap<(isize, isize), isize>) -> usize { diff --git a/source/day_06/mod.rs b/source/year_2021/day_06/mod.rs similarity index 72% rename from source/day_06/mod.rs rename to source/year_2021/day_06/mod.rs index 7a04923..cb6e7d4 100644 --- a/source/day_06/mod.rs +++ b/source/year_2021/day_06/mod.rs @@ -1,40 +1,36 @@ -use std::collections::HashMap; - -use color_eyre::Result; +use crate::prelude::*; type FishMap = HashMap; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_06.txt").trim(); - println!("Day 06 Part 1: {}", part_1(input_data)?); - println!("Day 06 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(6, 2021), part_1, part_2) + .with_expected(343441, 1569108373832_i64) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let mut fishes = parse_fishes(input)?; for _ in 0..80 { fishes = simulate_fishes(fishes); } - Ok(count_fishes(fishes)) + Ok(count_fishes(fishes).to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let mut fishes = parse_fishes(input)?; for _ in 0..256 { fishes = simulate_fishes(fishes); } - Ok(count_fishes(fishes)) + Ok(count_fishes(fishes).to_string()) } fn parse_fishes(input: &str) -> Result { let mut fishes = FishMap::new(); let individual_fishes = input - .split(",") + .split(',') .map(str::parse) .collect::, _>>()?; diff --git a/source/day_07/mod.rs b/source/year_2021/day_07/mod.rs similarity index 55% rename from source/day_07/mod.rs rename to source/year_2021/day_07/mod.rs index 71090f5..b817ead 100644 --- a/source/day_07/mod.rs +++ b/source/year_2021/day_07/mod.rs @@ -1,40 +1,39 @@ -use color_eyre::{eyre::eyre, Result}; +use crate::prelude::*; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_07.txt").trim(); - println!("Day 07 Part 1: {}", part_1(input_data)?); - println!("Day 07 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(7, 2021), part_1, part_2) + .with_expected(328318, 89791146) } fn parse_crabs(input: &str) -> Result<(Vec, isize)> { let crabs = input - .split(",") + .split(',') .map(str::parse) .collect::, _>>()?; let highest_crab = *crabs .iter() .max() - .ok_or(eyre!("Unable to find highest crab"))?; + .ok_or_else(|| eyre!("Unable to find highest crab"))?; Ok((crabs, highest_crab)) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let (crabs, highest_crab) = parse_crabs(input)?; (0..=highest_crab) .map(|target_position| { crabs .iter() .map(|crab| (target_position - crab).abs()) - .sum() + .sum::() }) .min() - .ok_or(eyre!("Unable to find lowest fuel")) + .map(|result| result.to_string()) + .ok_or_else(|| eyre!("Unable to find lowest fuel")) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let (crabs, highest_crab) = parse_crabs(input)?; (0..=highest_crab) .map(|target_position| { @@ -42,8 +41,9 @@ fn part_2(input: &str) -> Result { .iter() .map(|crab| (target_position - crab).abs()) .map(|steps| (steps * (steps + 1)) / 2) - .sum() + .sum::() }) .min() - .ok_or(eyre!("Unable to find lowest fuel")) + .map(|result| result.to_string()) + .ok_or_else(|| eyre!("Unable to find lowest fuel")) } diff --git a/source/day_08/display.rs b/source/year_2021/day_08/display.rs similarity index 100% rename from source/day_08/display.rs rename to source/year_2021/day_08/display.rs diff --git a/source/day_08/mod.rs b/source/year_2021/day_08/mod.rs similarity index 57% rename from source/day_08/mod.rs rename to source/year_2021/day_08/mod.rs index 6eea49b..30fc7c6 100644 --- a/source/day_08/mod.rs +++ b/source/year_2021/day_08/mod.rs @@ -1,17 +1,14 @@ -use color_eyre::{eyre::eyre, Result}; - mod display; use display::{CharSet, Display}; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_08.txt").trim(); - println!("Day 08 Part 1: {}", part_1(input_data)?); - println!("Day 08 Part 2: {}", part_2(input_data)?); - Ok(()) +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(8, 2021), part_1, part_2).with_expected(521, 1016804) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { Ok( input .lines() @@ -19,17 +16,18 @@ fn part_1(input: &str) -> Result { line .split(" | ") .nth(1) - .ok_or(eyre!("Invalid input: {}", line)) + .ok_or_else(|| eyre!("Invalid input: {}", line)) }) .collect::>>()? .into_iter() - .flat_map(|line| line.split(" ").map(Display::parse)) + .flat_map(|line| line.split(' ').map(Display::parse)) .filter(|display| [2, 4, 3, 7].contains(&display.0)) - .count(), + .count() + .to_string(), ) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let mut sum = 0; for line in input.lines() { @@ -39,8 +37,8 @@ fn part_2(input: &str) -> Result { let displays = Display::figure_out_from_others( split .next() - .ok_or(eyre!("Invalid input: {}", line))? - .split(" ") + .ok_or_else(|| eyre!("Invalid input: {}", line))? + .split(' ') .map(Display::parse) .collect::>(), )?; @@ -48,25 +46,24 @@ fn part_2(input: &str) -> Result { // Get all the CharSets from the encoded side. let encoded = split .next() - .ok_or(eyre!("Invalid input: {}", line))? - .split(" ") + .ok_or_else(|| eyre!("Invalid input: {}", line))? + .split(' ') .map(str::chars) - .map(CharSet::from_iter) - .collect::>(); + .map(CharSet::from_iter); // Loop through the encoded numbers backwards so we can use the loop index // to multiply it by 10 to the power of the index. // So 123 would be (3 * 1) + (2 * 10) + (1 * 100). - for (index, set) in encoded.into_iter().rev().enumerate() { + for (index, set) in encoded.rev().enumerate() { let decoded_number = displays .iter() .find(|display| display.1 == &set) .map(|display| display.0) - .ok_or(eyre!("Impossible to decode {:?}", set))?; + .ok_or_else(|| eyre!("Impossible to decode {:?}", set))?; sum += 10_isize.pow(index as u32) * decoded_number; } } - Ok(sum) + Ok(sum.to_string()) } diff --git a/source/day_09/mod.rs b/source/year_2021/day_09/mod.rs similarity index 79% rename from source/day_09/mod.rs rename to source/year_2021/day_09/mod.rs index 5032919..9356975 100644 --- a/source/day_09/mod.rs +++ b/source/year_2021/day_09/mod.rs @@ -1,13 +1,7 @@ -use std::collections::HashMap; +use crate::prelude::*; -use color_eyre::{eyre::eyre, Result}; -use itertools::Itertools; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_09.txt").trim(); - println!("Day 09 Part 1: {}", part_1(input_data)?); - println!("Day 09 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(9, 2021), part_1, part_2).with_expected(600, 987840) } type Coordinate = (isize, isize); @@ -15,10 +9,10 @@ type Coordinate = (isize, isize); type HeightMap = HashMap; const ADJACENT_OFFSETS: &[Coordinate] = &[ - (-1, 00), // Left - (01, 00), // Right - (00, -1), // Up - (00, 01), // Down + (-1, 0), // Left + (1, 0), // Right + (0, -1), // Up + (0, 1), // Down ]; fn parse_heightmap(input: &str) -> Result { @@ -28,7 +22,7 @@ fn parse_heightmap(input: &str) -> Result { for (x, height) in line.char_indices() { let height = height .to_digit(10) - .ok_or(eyre!("Invalid input: {}", line))? + .ok_or_else(|| eyre!("Invalid input: {}", line))? .try_into()?; height_map.insert((x.try_into()?, y.try_into()?), height); @@ -44,7 +38,7 @@ fn parse_map_bounds(input: &str) -> Result { .lines() .next() .map(|line| line.chars().count()) - .ok_or(eyre!("Invalid input: {}", input))? + .ok_or_else(|| eyre!("Invalid input: {}", input))? .try_into()?, input.lines().count().try_into()?, )) @@ -80,7 +74,7 @@ fn discover_basins( coordinates } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let height_map = parse_heightmap(input)?; let map_bounds = parse_map_bounds(input)?; let mut risk_level = 0; @@ -90,7 +84,7 @@ fn part_1(input: &str) -> Result { let coordinate = (x, y); let height = height_map .get(&coordinate) - .ok_or(eyre!("Coordinate {:?} not found", coordinate))?; + .ok_or_else(|| eyre!("Coordinate {:?} not found", coordinate))?; for (x_offset, y_offset) in ADJACENT_OFFSETS { let neighbor = (x_offset + x, y_offset + y); @@ -106,10 +100,10 @@ fn part_1(input: &str) -> Result { } } - Ok(risk_level) + Ok(risk_level.to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let height_map = parse_heightmap(input)?; let map_bounds = parse_map_bounds(input)?; let mut basins = vec![]; @@ -123,7 +117,7 @@ fn part_2(input: &str) -> Result { let height = height_map .get(&coordinate) - .ok_or(eyre!("Coordinate {:?} not found", coordinate))?; + .ok_or_else(|| eyre!("Coordinate {:?} not found", coordinate))?; if height == &9 { continue 'x_loop; } @@ -145,6 +139,7 @@ fn part_2(input: &str) -> Result { .into_iter() .sorted_by(|a, b| b.cmp(a)) .take(3) - .product(), + .product::() + .to_string(), ) } diff --git a/source/day_10/mod.rs b/source/year_2021/day_10/mod.rs similarity index 75% rename from source/day_10/mod.rs rename to source/year_2021/day_10/mod.rs index baa1285..32ea991 100644 --- a/source/day_10/mod.rs +++ b/source/year_2021/day_10/mod.rs @@ -1,15 +1,11 @@ -use std::collections::VecDeque; +use crate::prelude::*; -use color_eyre::Result; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_10.txt").trim(); - println!("Day 10 Part 1: {}", part_1(input_data)?); - println!("Day 10 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(10, 2021), part_1, part_2) + .with_expected(387363, 4330777059_i64) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let mut syntax_error_score = 0; for line in input.lines() { @@ -41,14 +37,14 @@ fn part_1(input: &str) -> Result { } } - Ok(syntax_error_score) + Ok(syntax_error_score.to_string()) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let mut scores = vec![]; 'line_loop: for line in input.lines() { - let mut line_score = 0; + let mut line_score: isize = 0; let mut tokens = VecDeque::new(); for token in line.chars() { @@ -86,6 +82,6 @@ fn part_2(input: &str) -> Result { scores.push(line_score); } - scores.sort_by(|a, b| a.cmp(b)); - Ok(scores[scores.len() / 2]) + scores.sort(); + Ok(scores[scores.len() / 2].to_string()) } diff --git a/source/day_13/canvas.rs b/source/year_2021/day_13/canvas.rs similarity index 100% rename from source/day_13/canvas.rs rename to source/year_2021/day_13/canvas.rs diff --git a/source/day_13/mod.rs b/source/year_2021/day_13/mod.rs similarity index 62% rename from source/day_13/mod.rs rename to source/year_2021/day_13/mod.rs index 4eddbf0..2289e19 100644 --- a/source/day_13/mod.rs +++ b/source/year_2021/day_13/mod.rs @@ -1,17 +1,20 @@ -use std::collections::HashSet; - -use color_eyre::{eyre::eyre, Result}; -use itertools::Itertools; - mod canvas; use canvas::{Canvas, Fold, Point}; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_13.txt").trim(); - println!("Day 13 Part 1: {}", part_1(input_data)?); - println!("Day 13 Part 2:\n{}", part_2(input_data)?); - Ok(()) +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(13, 2021), part_1, part_2).with_expected( + "712", + " ███ █ █ █ ████ ██ ███ ██ ████ + █ █ █ █ █ █ █ █ █ █ █ + ███ █ ████ ███ █ █ █ █ ███ + █ █ █ █ █ █ █ ███ █ █ + █ █ █ █ █ █ █ █ █ █ █ █ + ███ ████ █ █ █ ██ █ ██ █ +", + ) } fn parse(input: &str) -> Result<(Canvas, Vec)> { @@ -25,16 +28,16 @@ fn parse(input: &str) -> Result<(Canvas, Vec)> { let mut folds = vec![]; for line in input.lines() { - if line == "" { + if line.is_empty() { in_folds_section = true; continue; } if in_folds_section { - let mut split = line.split("="); + let mut split = line.split('='); let is_x_axis = split .next() - .map(|s| s.ends_with("x")) + .map(|s| s.ends_with('x')) .ok_or_else(|| eyre!("Invalid line: {}", line))?; let amount = split @@ -49,7 +52,7 @@ fn parse(input: &str) -> Result<(Canvas, Vec)> { } } else { let (x, y) = line - .split(",") + .split(',') .tuples() .next() .ok_or_else(|| eyre!("Invalid line: {}", line))?; @@ -71,10 +74,10 @@ fn parse(input: &str) -> Result<(Canvas, Vec)> { Ok((canvas, folds)) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let (mut canvas, folds) = parse(input)?; canvas = canvas.fold(&folds[0]); - Ok(canvas.points.into_iter().count()) + Ok(canvas.points.len().to_string()) } fn part_2(input: &str) -> Result { diff --git a/source/day_14/mod.rs b/source/year_2021/day_14/mod.rs similarity index 75% rename from source/day_14/mod.rs rename to source/year_2021/day_14/mod.rs index 43ec255..c564952 100644 --- a/source/day_14/mod.rs +++ b/source/year_2021/day_14/mod.rs @@ -1,16 +1,11 @@ -use std::collections::HashMap; - -use color_eyre::{eyre::eyre, Result}; -use itertools::Itertools; +use crate::prelude::*; type PairMap = HashMap<(char, char), char>; type PairCounts = HashMap<(char, char), isize>; -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_14.txt").trim(); - println!("Day 14 Part 1: {}", part_1(input_data)?); - println!("Day 14 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(14, 2021), part_1, part_2) + .with_expected(3411, 7477815755570_i64) } fn parse(input: &str) -> Result<(String, PairMap)> { @@ -60,7 +55,7 @@ fn count_totals(counts: &PairCounts) -> HashMap { totals } -fn run(input: &str, steps: isize) -> Result { +fn run(input: &str, steps: isize) -> Result { let (template, pairs) = parse(input)?; let mut counts = PairCounts::new(); @@ -80,18 +75,15 @@ fn run(input: &str, steps: isize) -> Result { .clone() .next() .ok_or_else(|| eyre!("No minimum found"))?; - let (_, max) = totals - .clone() - .last() - .ok_or_else(|| eyre!("No maximum found"))?; + let (_, max) = totals.last().ok_or_else(|| eyre!("No maximum found"))?; - Ok(max - min) + Ok((max - min).to_string()) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { run(input, 10) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { run(input, 40) } diff --git a/source/day_15/mod.rs b/source/year_2021/day_15/mod.rs similarity index 75% rename from source/day_15/mod.rs rename to source/year_2021/day_15/mod.rs index c2dbfdc..b506787 100644 --- a/source/day_15/mod.rs +++ b/source/year_2021/day_15/mod.rs @@ -1,13 +1,7 @@ -use std::collections::HashMap; +use crate::prelude::*; -use color_eyre::{eyre::eyre, Result}; -use pathfinding::prelude::{absdiff, astar}; - -pub fn solve() -> Result<()> { - let input_data = include_str!("../../data/day_15.txt").trim(); - println!("Day 15 Part 1: {}", part_1(input_data)?); - println!("Day 15 Part 2: {}", part_2(input_data)?); - Ok(()) +pub fn solution() -> Solution { + Solution::new(Day::new(15, 2021), part_1, part_2).with_expected(386, 2806) } type Grid = HashMap; @@ -16,8 +10,8 @@ type Grid = HashMap; struct Coordinate(isize, isize); impl Coordinate { - fn distance(&self, target: &Coordinate) -> isize { - absdiff(self.0, target.0) + absdiff(self.1, target.1) + fn distance(&self, target: &Coordinate) -> usize { + self.0.abs_diff(target.0) + self.1.abs_diff(target.1) } fn successors(&self, grid: &Grid) -> Vec<(Coordinate, isize)> { @@ -80,25 +74,26 @@ fn enlarge_grid(grid: Grid, end: Coordinate) -> (Grid, Coordinate) { (larger_grid, Coordinate(width * 5, height * 5)) } -fn run(grid: Grid, end: Coordinate) -> Result { +fn run(grid: Grid, end: Coordinate) -> Result { Ok( astar( &Coordinate(1, 1), |p| p.successors(&grid), - |p| p.distance(&end), + |p| p.distance(&end).try_into().unwrap(), |p| p == &end, ) .ok_or_else(|| eyre!("No path found"))? - .1, + .1 + .to_string(), ) } -fn part_1(input: &str) -> Result { +fn part_1(input: &str) -> Result { let (grid, end) = parse(input)?; run(grid, end) } -fn part_2(input: &str) -> Result { +fn part_2(input: &str) -> Result { let (grid, end) = parse(input)?; let (grid, end) = enlarge_grid(grid, end); run(grid, end) diff --git a/source/year_2021/mod.rs b/source/year_2021/mod.rs new file mode 100644 index 0000000..ffc27af --- /dev/null +++ b/source/year_2021/mod.rs @@ -0,0 +1,33 @@ +use crate::solution::Solution; + +mod day_01; +mod day_02; +mod day_03; +mod day_04; +mod day_05; +mod day_06; +mod day_07; +mod day_08; +mod day_09; +mod day_10; +mod day_13; +mod day_14; +mod day_15; + +pub fn get_solutions() -> Vec { + vec![ + day_01::solution(), + day_02::solution(), + day_03::solution(), + day_04::solution(), + day_05::solution(), + day_06::solution(), + day_07::solution(), + day_08::solution(), + day_09::solution(), + day_10::solution(), + day_13::solution(), + day_14::solution(), + day_15::solution(), + ] +}