├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── all_days.rs ├── input ├── 10 │ └── input.txt ├── 11 │ └── input.txt ├── 12 │ └── input.txt ├── 13 │ └── input.txt ├── 14 │ └── input.txt ├── 15 │ └── input.txt ├── 16 │ └── input.txt ├── 17 │ └── input.txt ├── 18 │ └── input.txt ├── 19 │ └── input.txt ├── 20 │ └── input.txt ├── 21 │ └── input.txt ├── 22 │ └── input.txt ├── 23 │ └── input.txt ├── 24 │ └── input.txt ├── 25 │ └── input.txt ├── 01 │ └── input.txt ├── 02 │ └── input.txt ├── 03 │ └── input.txt ├── 04 │ └── input.txt ├── 05 │ └── input.txt ├── 06 │ └── input.txt ├── 07 │ └── input.txt ├── 08 │ └── input.txt └── 09 │ └── input.txt ├── rust-toolchain.toml └── src ├── bin.rs ├── day01 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day02 ├── input.rs ├── mod.rs ├── part1.rs ├── part2.rs └── shared.rs ├── day03 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day04 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day05 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day06 ├── input.rs ├── mod.rs ├── part1.rs ├── part2.rs └── shared.rs ├── day07 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day08 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day09 ├── input.rs ├── mod.rs ├── part1.rs ├── part2.rs └── shared.rs ├── day10 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day11 ├── input.rs ├── mod.rs ├── part1.rs ├── part2.rs └── shared.rs ├── day12 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day13 ├── input.rs ├── mod.rs ├── part1.rs ├── part2.rs └── shared.rs ├── day14 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day15 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day16 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day17 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day18 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day19 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day20 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day21 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day22 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day23 ├── grid.rs ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day24 ├── input.rs ├── mod.rs ├── part1.rs └── part2.rs ├── day25 ├── input.rs ├── mod.rs └── part1.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *:Zone.Identifier 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advent_of_code_2022" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "aoc2022lib" 8 | path = "src/lib.rs" 9 | 10 | [[bin]] 11 | name = "aoc2022bin" 12 | path = "src/bin.rs" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | anyhow = "1.0.66" 18 | clap = { version = "4.0.22", features = ["derive"] } 19 | criterion = { version = "0.4.0", features = ["html_reports"] } 20 | itertools = "0.10.5" 21 | nom = "7.1.1" 22 | rayon = "1.6.1" 23 | 24 | [[bench]] 25 | name = "all_days" 26 | harness = false 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eric's Advent of Code 2022 Solutions 2 | 3 | ## The Blog 4 | 5 | For the last two years, I've blogged my approaches to the Advent of Code puzzles on my 6 | [personal site](https://www.ericburden.work/blog/). Assuming I hold true to form, each 7 | blog post will include code and commentary on my thinking behind the approach, my thoughts 8 | about the puzzles, and vain attempts at wit. 9 | 10 | ## Project Structure 11 | 12 | This year, I'm using Rust! I solved 2019's puzzles in Rust after the fact (it's how I 13 | learned Rust to begin with), but this year I'll solve each day in Rust first. I've 14 | set up folders for each day's code and input files like so: 15 | 16 | ``` 17 | 18 | ├─benches 19 | │ └─all_days.rs 20 | ├─input 21 | │ └─XX 22 | │ ├─input.txt 23 | │ └─test.txt 24 | ├─src 25 | │ ├─dayXX 26 | │ │ ├─input.rs 27 | │ │ ├─mod.rs 28 | │ │ ├─part1.rs 29 | │ │ └─part2.rs 30 | │ ├─bin.rs 31 | │ └─lib.rs 32 | ├─Cargo.toml 33 | └─README.md 34 | ``` 35 | 36 | There are a few organizational notes to point out here: 37 | 38 | - The `mod.rs` file for each day defines `Input` as a type alias for the type the 39 | input file will be parsed into, and a convenience function `run(_: Part) -> Output` 40 | that reads in the input and solves for either part one or part two, depending on the 41 | variant of `Part` that is passed and returns the result as an Output (for consistency). 42 | This file also contains the tests that cofirm the answer once it has been found. 43 | - `Output` is an enum with variants for `u32`, `i32`, `u64, `i64`, and `String`. This 44 | allows the binary to expect the same (printable) type from each day's solution. 45 | - Input files are being included in each day's `input.rs` via the `include_str!()` macro, 46 | which means parsing will be on the file contents as one long, newline-separated, string 47 | slice. The main entrypoint for input parsing is the `read() -> Input` function which 48 | takes no arguments (relying on the included `INPUT` constant) and returns the parsed 49 | input file. 50 | - The `part1.rs` and `part2.rs` files each contain a `solve(_: &Input) -> Output` function 51 | that takes a reference to the parsed input and returns the solution for that part of 52 | that day. 53 | 54 | ## Usage 55 | 56 | Most of the functionality of this project shell is best accessed via `cargo` (though you can 57 | install the project if you really want to). 58 | 59 | - `cargo test` to run the tests. Full documentation for that command [here](https://doc.rust-lang.org/cargo/commands/cargo-test.html) 60 | - `cargo bench` to run the benchmarks. Full documentation for that command [here](https://bheisler.github.io/criterion.rs/book/user_guide/command_line_options.html) 61 | - `cargo run` to run the first day's solutions and print the results. `cargo run ` to run the day's solutions and print the results. 62 | 63 | 64 | -------------------------------------------------------------------------------- /input/06/input.txt: -------------------------------------------------------------------------------- 1 | dfsfmfbbbjnbbpddfcfjcjbjwjqqbtbntnhtnncfnfpnffwpphwppvbvtvztzszfzhhnqnvnpppmzmczcmzczbznnbssbhhvghhzqzvzttjvtvcccdhccmvccgdgttghthppwlwqlwwcswspsfsdfddhwwzlwzwttlglttsjjthhgcgfcfhfhtftrffwcfcsfccnntmmpvpgpfgfsgfflgfgmffbmmqsmqmmcqqmjjzczwczzpjzzgtgnnqqvdqvqllbttftgtztcztttlslrslrlfldljlwjjnvjjmrrhdrhhflhffgnfnjffmvmvjjspsvvrcchhgngwwdwbwwfhwffmggbsgspsjsfjjdwwbtbdtbdbgdbbbzhzvhhwpwlwfwfswwrgggmggssfwwdrwrccssjttltrrnggdbggvzzzrfzfwzfffnfnmnnzmnznpzzdtzdttbvvgffmnfmfmbbqppcgpgtgpgggcwwlpplslbslblhlmlfmlmldmmjhjjgllglhlssswcssprrqrjrhrvrffpfnpfnppzgztgtdgtdggftgftggqrgrnnqpnpgnpnggsjsvscsnswshwswfwfwqffcrrmprrrcgrrggmnnmzmwzmwzzgzzhnzzthtggtmmdrmrbmmqwwjswwphhzvvjqjrrvzrrmssdvdlvvlhvvjggbwbrwwjqjqmmfllttvpttdvtdtddrttpltpllzrlrwlrrvcvpcphcccqwwtpwwbmmtntfntftvfvfqvvwnvwwvhhlccvrcrlclcqcvqqsvvfjjqmmfhfvvctczzlhlsslpspfsswnsnzsspzpccmssmpmbpbmmvnvsnvsszttrppvgpgnncllrvlrvllcvlvwvhvghgvvnhvvmffclfccvwcvccrwrhhrffqrqcrqqhbqhqssmzsmzsmmzrztzllmclmmbgmbbrmrgrwrgwrrgngvvtqqpbqpppgbgccsjjcmmtdmdjmmjcchvchhbccnjngjjnwjnnrvnrrsqshshszzqnngbnnrnggvwwlzwwlqwqmqdqrdqqrhqhffpgffqgqdggfjgglfglgvgjvjmjtthghrhvhgvhvnncnwcnwntwntnjjqjwwrdwddsggfddnhnsnvsstbbjddfhddhbddwbwbnbrnnrhrqhqdhdllvbbnzzbmbttpjjngnqqcffrsrnsnnhzzgllgvlvcchfhzzmzrmrggdvgvgqqwnqqpdqqvjjvnvhnhhgvhhgllhlzlclbcccwppztzhhvmvzzzsbzbppvdvdtdtdvdsdlsscnnqhnhgngghrrzprzzpddmvvhcvvprplpnlppscctzthtptspttmftmftfjjdfdjfjrfrfbfcchmhnnbddwzwvvpvrvnnslnlnhlnhlltslttqpqvvgzvzsstcstsrtrbtbbzmmzrzqrzqqnmqnnpjpttwgtgzgqqgmgnnzgzrggpbggvssqvssmhmshmssvlvlmvvnhnhddwbbllffgbffbbztthbhdhdghhrccfmfrfmmbdbfdbffzfgfrfqqptpgpjjlvvbjjdzdbbszslzlldnngwgddbmbpbwwhphnhmhhlthhgfhfvvpmvmhvmvdvsddsjtzvfmpsrwrrzgcvnnllfjmvfptwncppfmgqbfzrdpnfddghsqfmnqfwfslrsgjmqtfqwhdddsbhtbtpswcbfppcbhzfzbsqljzndcsrlhrrtstgfhhfsqqrwgnncsmstdmjvfjhqnrczlftzzzhqdzjdcdqcgfpmbqntdhzcvbtpssrvmgjwzwfvtpsrsrwrvrsjgrmzqzvbttscldsnnwzvmlztnnpdjrwvhshpdwgvhmlrnhtfccjnldlnhtfncfjjjztjmhrdqpvhggtqzwjsvwdzhdmwhsmgzjcwzqzlwbrlzsmlwhpjvflnppvrbgrsblmjpnqvgpjbpwbjgjqzwvjbgcplccjgbfwlblzfjqpwszbqbcnlbmfmqpmgspscgfdgfwnmcdzcqnjznndjcvlblszcnpflbjqltpfzhffdbwbshtpnwwlspltpcrvbdtflwbjrfnvrflqpgqtjzqwmmsdvtsmgjtrtbrzchwhpfsznjqcbrjcvwqgrcsqpvfzhrdlmnvvhjzpgpnlrmqfvcnlrlcfjblfcgvngdjfdczsrtnnwjndsfcsdlhdnbtplfnhsmmbldmsjwcblghhgqwbnjvqbqhddrmrtncvwnchsfpddzgrrtzntmwnmdwlrvnjgnzjqvptztnqnqmcjmmrhmtstgdvhffbbmphnbtsdmpmscsfdbnfnchrhhjpsfhhswfszgqfcbdbgnrqhrflpfgfgdcrjvrwbvsfmzzhvzvqzgshcqzlfcljnlgshdlhwdchhhvwlshwdrgjfbnptqqglbpcfgrmqjhqvlbzdwgnzfzlpcpjzqwhbfjljszvjdsrmfzntgnjflhnwhpfrlbpvgzmbqwzlgphmbvbfdqfgqqbhzzvrjftnwjzhlrqccwcfzvntscnbfcsrqqnvlvhszpgwtrzjrqbtslctbhtbczwtmsgwczncbjmzqvnthpwjmsbsjnfpsmghnvtqjjnjfwtnmlthrlcpqhjpnvnbbnwrdjfshwhpdwmsbngfhbhsqphlqspcgzwrgfjmqqtlsfgnvqtdgnhmdvvqzjwlhsnvjczbssrnlhwdmdthmtprjjfttfzbbswfwsvvslfnbcvprzhtcqwdrzjrnjjctqfsjrsddlhzcnstqfppjlqhvcbjbfwndwdtdfvnlwgvdvhzzrqthdhdmddfdwschmpwwrnlgsldzhgjrlmtzrnrrtqfctvbncpcjlsvnwjvhgnbshhwlqhtjghvrctlvngjgjrlgshhwscrdvzjqtfrrbssvqlcjjdljpmlzfqqnqmffpsbvgcqzqdjwczbqwjgfvpdgjglnqdshppcsqcmszhrbcpnhjnczlhwsbnfnzsczjvmftngqcvhgpgwlzbmjnmmdvdjfrcwnjrncvfstrvvqsstphmqdpwjqhzgppmgwlgjhgfwqgmjrlsvqpfqznvbqzgtngvpbpttzvngjwtrjgdnvdggzlnmpgbzhtnfnrhgwvhnpqdwfdvvftzllpqblgdglclwtwbchlvwcmmvtchlntlghztfvgfjczcbqmzgnqmrjcmqvjmjhfpztjcvclblmrctzfmsdfvfpwdcbsglgrsjqcgtcblhbtgcgjlwhhqnwhdnwzlhvphtvmlfnbfmgpnnbzqtdtzqrbhfljlwstlbscnrsvhrbrcthvzcngrttddcjqhtmsfpsgldgtsgjtprsttlssmrrfjmrddqvnqcfmphbnjtdsqvptrdzqbqfjqtnrqtjgdpbrlzrlvwcbcqbcmncfmwcpdhgpjdrdcmrqnflsqbllrqslmhsljwghnwcjwvhchcnlgppmphbqtcdfzjpcqcgsjzvmgfjgfsvmvjfqvtpffbpmhnnnrjmqhhhrhrqfqdwzdvzssslzvqhngdpszztgrvjntcpzhbfmhbpvcndsjbtnwgpmztpbrtjmfqrsvndrspdqmlsbldgghfflncszhnsttfslvwhvfnsmmhbbvjqslsjfqplndndwmbmvgchgvhzclrcnhvgbgmpctrggvqpvqvgvncmdwhpmwhzwhlgsnlnwggfbbvdvqrrsmhwzrrpgrjfshzgzjpfwjhpqmqhbjlwhwsfszlshpzprvgprlprrvlcrmbttjrpqsrdcdfwdrzbcfjpvrlrjjdwhbspqmrblvtldqdhtjtjphpqswgvqfftdgqrtjgsmthlhvlcqrwlqtthwjgrcpwcnsqtssqzpzqptrwjjdfchfmmsrsccnlvqbdmbcdjmhpgvnnlttfhggfphvbwqtcztbnsflztcfpbcpjbcmsplhjdbsmzhgnmfrhscmwmfqbljvhgllvvgqzphzbswdzlhmpcvnntczrcnqvlphhjdjjjnhfzzcjjsdlfccwvswvjfgvmlnpvjvcbpglsgtpj 2 | -------------------------------------------------------------------------------- /input/10/input.txt: -------------------------------------------------------------------------------- 1 | noop 2 | noop 3 | addx 5 4 | addx 21 5 | addx -16 6 | noop 7 | addx 1 8 | noop 9 | noop 10 | addx 4 11 | addx 1 12 | addx 4 13 | addx 1 14 | noop 15 | addx 4 16 | addx -9 17 | noop 18 | addx 19 19 | addx -5 20 | noop 21 | noop 22 | addx 5 23 | addx 1 24 | addx -38 25 | addx 5 26 | addx -2 27 | addx 2 28 | noop 29 | noop 30 | addx 7 31 | addx 9 32 | addx 20 33 | addx -3 34 | addx -18 35 | addx 2 36 | addx 5 37 | noop 38 | noop 39 | addx -2 40 | noop 41 | noop 42 | addx 7 43 | addx 3 44 | addx -2 45 | addx 2 46 | addx -28 47 | addx -7 48 | addx 5 49 | noop 50 | addx 2 51 | addx 32 52 | addx -27 53 | noop 54 | noop 55 | noop 56 | noop 57 | noop 58 | addx 7 59 | noop 60 | addx 22 61 | addx -19 62 | noop 63 | addx 5 64 | noop 65 | addx -7 66 | addx 17 67 | addx -7 68 | noop 69 | addx -20 70 | addx 27 71 | noop 72 | addx -16 73 | addx -20 74 | addx 1 75 | noop 76 | addx 3 77 | addx 15 78 | addx -8 79 | addx -2 80 | addx -6 81 | addx 14 82 | addx 4 83 | noop 84 | noop 85 | addx -17 86 | addx 22 87 | noop 88 | addx 5 89 | noop 90 | noop 91 | noop 92 | addx 2 93 | noop 94 | addx 3 95 | addx -32 96 | addx -5 97 | noop 98 | addx 4 99 | addx 3 100 | addx -2 101 | addx 34 102 | addx -27 103 | addx 5 104 | addx 16 105 | addx -18 106 | addx 7 107 | noop 108 | addx -2 109 | addx -1 110 | addx 8 111 | addx 14 112 | addx -9 113 | noop 114 | addx -15 115 | addx 16 116 | addx 2 117 | addx -35 118 | noop 119 | noop 120 | noop 121 | noop 122 | addx 3 123 | addx 4 124 | noop 125 | addx 1 126 | addx 4 127 | addx 1 128 | noop 129 | addx 4 130 | addx 2 131 | addx 3 132 | addx -5 133 | addx 19 134 | addx -9 135 | addx 2 136 | addx 4 137 | noop 138 | noop 139 | noop 140 | noop 141 | addx 3 142 | addx 2 143 | noop 144 | noop 145 | noop 146 | -------------------------------------------------------------------------------- /input/11/input.txt: -------------------------------------------------------------------------------- 1 | Monkey 0: 2 | Starting items: 97, 81, 57, 57, 91, 61 3 | Operation: new = old * 7 4 | Test: divisible by 11 5 | If true: throw to monkey 5 6 | If false: throw to monkey 6 7 | 8 | Monkey 1: 9 | Starting items: 88, 62, 68, 90 10 | Operation: new = old * 17 11 | Test: divisible by 19 12 | If true: throw to monkey 4 13 | If false: throw to monkey 2 14 | 15 | Monkey 2: 16 | Starting items: 74, 87 17 | Operation: new = old + 2 18 | Test: divisible by 5 19 | If true: throw to monkey 7 20 | If false: throw to monkey 4 21 | 22 | Monkey 3: 23 | Starting items: 53, 81, 60, 87, 90, 99, 75 24 | Operation: new = old + 1 25 | Test: divisible by 2 26 | If true: throw to monkey 2 27 | If false: throw to monkey 1 28 | 29 | Monkey 4: 30 | Starting items: 57 31 | Operation: new = old + 6 32 | Test: divisible by 13 33 | If true: throw to monkey 7 34 | If false: throw to monkey 0 35 | 36 | Monkey 5: 37 | Starting items: 54, 84, 91, 55, 59, 72, 75, 70 38 | Operation: new = old * old 39 | Test: divisible by 7 40 | If true: throw to monkey 6 41 | If false: throw to monkey 3 42 | 43 | Monkey 6: 44 | Starting items: 95, 79, 79, 68, 78 45 | Operation: new = old + 3 46 | Test: divisible by 3 47 | If true: throw to monkey 1 48 | If false: throw to monkey 3 49 | 50 | Monkey 7: 51 | Starting items: 61, 97, 67 52 | Operation: new = old + 4 53 | Test: divisible by 17 54 | If true: throw to monkey 0 55 | If false: throw to monkey 5 56 | -------------------------------------------------------------------------------- /input/15/input.txt: -------------------------------------------------------------------------------- 1 | Sensor at x=3772068, y=2853720: closest beacon is at x=4068389, y=2345925 2 | Sensor at x=78607, y=2544104: closest beacon is at x=-152196, y=4183739 3 | Sensor at x=3239531, y=3939220: closest beacon is at x=3568548, y=4206192 4 | Sensor at x=339124, y=989831: closest beacon is at x=570292, y=1048239 5 | Sensor at x=3957534, y=2132743: closest beacon is at x=3897332, y=2000000 6 | Sensor at x=1882965, y=3426126: closest beacon is at x=2580484, y=3654136 7 | Sensor at x=1159443, y=3861139: closest beacon is at x=2580484, y=3654136 8 | Sensor at x=2433461, y=287013: closest beacon is at x=2088099, y=-190228 9 | Sensor at x=3004122, y=3483833: closest beacon is at x=2580484, y=3654136 10 | Sensor at x=3571821, y=799602: closest beacon is at x=3897332, y=2000000 11 | Sensor at x=2376562, y=1539540: closest beacon is at x=2700909, y=2519581 12 | Sensor at x=785113, y=1273008: closest beacon is at x=570292, y=1048239 13 | Sensor at x=1990787, y=38164: closest beacon is at x=2088099, y=-190228 14 | Sensor at x=3993778, y=3482849: closest beacon is at x=4247709, y=3561264 15 | Sensor at x=3821391, y=3986080: closest beacon is at x=3568548, y=4206192 16 | Sensor at x=2703294, y=3999015: closest beacon is at x=2580484, y=3654136 17 | Sensor at x=1448314, y=2210094: closest beacon is at x=2700909, y=2519581 18 | Sensor at x=3351224, y=2364892: closest beacon is at x=4068389, y=2345925 19 | Sensor at x=196419, y=3491556: closest beacon is at x=-152196, y=4183739 20 | Sensor at x=175004, y=138614: closest beacon is at x=570292, y=1048239 21 | Sensor at x=1618460, y=806488: closest beacon is at x=570292, y=1048239 22 | Sensor at x=3974730, y=1940193: closest beacon is at x=3897332, y=2000000 23 | Sensor at x=2995314, y=2961775: closest beacon is at x=2700909, y=2519581 24 | Sensor at x=105378, y=1513086: closest beacon is at x=570292, y=1048239 25 | Sensor at x=3576958, y=3665667: closest beacon is at x=3568548, y=4206192 26 | Sensor at x=2712265, y=2155055: closest beacon is at x=2700909, y=2519581 27 | -------------------------------------------------------------------------------- /input/16/input.txt: -------------------------------------------------------------------------------- 1 | Valve AA has flow rate=0; tunnels lead to valves RZ, QQ, FH, IM, VJ 2 | Valve FE has flow rate=0; tunnels lead to valves TM, TR 3 | Valve QZ has flow rate=19; tunnels lead to valves HH, OY 4 | Valve TU has flow rate=17; tunnels lead to valves NJ, IN, WN 5 | Valve RG has flow rate=0; tunnels lead to valves IK, SZ 6 | Valve TM has flow rate=0; tunnels lead to valves FE, JH 7 | Valve JH has flow rate=4; tunnels lead to valves NW, QQ, TM, VH, AZ 8 | Valve NW has flow rate=0; tunnels lead to valves JH, OB 9 | Valve BZ has flow rate=0; tunnels lead to valves XG, XF 10 | Valve VS has flow rate=0; tunnels lead to valves FF, GC 11 | Valve OI has flow rate=20; tunnel leads to valve SY 12 | Valve IK has flow rate=0; tunnels lead to valves RG, TR 13 | Valve RO has flow rate=0; tunnels lead to valves UZ, YL 14 | Valve LQ has flow rate=0; tunnels lead to valves IZ, PA 15 | Valve GG has flow rate=18; tunnels lead to valves GH, VI 16 | Valve NJ has flow rate=0; tunnels lead to valves TU, UZ 17 | Valve SY has flow rate=0; tunnels lead to valves OI, ZL 18 | Valve HH has flow rate=0; tunnels lead to valves QZ, WN 19 | Valve RZ has flow rate=0; tunnels lead to valves AA, UZ 20 | Valve OF has flow rate=0; tunnels lead to valves YL, IZ 21 | Valve IZ has flow rate=9; tunnels lead to valves OF, FH, VH, JZ, LQ 22 | Valve OB has flow rate=0; tunnels lead to valves UZ, NW 23 | Valve AH has flow rate=0; tunnels lead to valves FF, ZL 24 | Valve ZL has flow rate=11; tunnels lead to valves SY, VI, AH 25 | Valve BF has flow rate=0; tunnels lead to valves PA, YL 26 | Valve OH has flow rate=0; tunnels lead to valves CU, JZ 27 | Valve VH has flow rate=0; tunnels lead to valves IZ, JH 28 | Valve AZ has flow rate=0; tunnels lead to valves JC, JH 29 | Valve XG has flow rate=0; tunnels lead to valves BZ, PA 30 | Valve OY has flow rate=0; tunnels lead to valves PZ, QZ 31 | Valve IM has flow rate=0; tunnels lead to valves FM, AA 32 | Valve GO has flow rate=0; tunnels lead to valves VJ, TR 33 | Valve YL has flow rate=8; tunnels lead to valves JC, RO, OF, BF, FM 34 | Valve TY has flow rate=0; tunnels lead to valves SZ, TS 35 | Valve UZ has flow rate=5; tunnels lead to valves OB, NJ, RO, RZ, GC 36 | Valve XF has flow rate=21; tunnel leads to valve BZ 37 | Valve RY has flow rate=0; tunnels lead to valves TR, FF 38 | Valve QQ has flow rate=0; tunnels lead to valves JH, AA 39 | Valve TS has flow rate=0; tunnels lead to valves TY, FF 40 | Valve GC has flow rate=0; tunnels lead to valves VS, UZ 41 | Valve JC has flow rate=0; tunnels lead to valves AZ, YL 42 | Valve JZ has flow rate=0; tunnels lead to valves IZ, OH 43 | Valve IN has flow rate=0; tunnels lead to valves TH, TU 44 | Valve FM has flow rate=0; tunnels lead to valves IM, YL 45 | Valve FH has flow rate=0; tunnels lead to valves AA, IZ 46 | Valve VJ has flow rate=0; tunnels lead to valves AA, GO 47 | Valve TH has flow rate=0; tunnels lead to valves CU, IN 48 | Valve TR has flow rate=7; tunnels lead to valves FE, IK, RY, GO 49 | Valve GH has flow rate=0; tunnels lead to valves GG, FF 50 | Valve SZ has flow rate=10; tunnels lead to valves RG, TY 51 | Valve PA has flow rate=16; tunnels lead to valves XG, LQ, BF 52 | Valve PZ has flow rate=0; tunnels lead to valves CU, OY 53 | Valve VI has flow rate=0; tunnels lead to valves ZL, GG 54 | Valve CU has flow rate=22; tunnels lead to valves PZ, OH, TH 55 | Valve WN has flow rate=0; tunnels lead to valves TU, HH 56 | Valve FF has flow rate=13; tunnels lead to valves VS, RY, AH, TS, GH 57 | -------------------------------------------------------------------------------- /input/19/input.txt: -------------------------------------------------------------------------------- 1 | Blueprint 1: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 15 clay. Each geode robot costs 2 ore and 8 obsidian. 2 | Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 10 obsidian. 3 | Blueprint 3: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 14 obsidian. 4 | Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 4 ore and 15 obsidian. 5 | Blueprint 5: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 3 ore and 15 obsidian. 6 | Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 7 obsidian. 7 | Blueprint 7: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 9 clay. Each geode robot costs 3 ore and 7 obsidian. 8 | Blueprint 8: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 8 obsidian. 9 | Blueprint 9: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 4 ore and 18 obsidian. 10 | Blueprint 10: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 11 clay. Each geode robot costs 2 ore and 19 obsidian. 11 | Blueprint 11: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 3 ore and 10 obsidian. 12 | Blueprint 12: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 16 obsidian. 13 | Blueprint 13: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 3 ore and 15 obsidian. 14 | Blueprint 14: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 3 ore and 13 obsidian. 15 | Blueprint 15: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 2 ore and 20 obsidian. 16 | Blueprint 16: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 14 clay. Each geode robot costs 4 ore and 10 obsidian. 17 | Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian. 18 | Blueprint 18: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 17 obsidian. 19 | Blueprint 19: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 4 ore and 12 obsidian. 20 | Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 20 obsidian. 21 | Blueprint 21: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 12 obsidian. 22 | Blueprint 22: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 3 ore and 19 obsidian. 23 | Blueprint 23: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 3 ore and 8 obsidian. 24 | Blueprint 24: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 13 clay. Each geode robot costs 2 ore and 9 obsidian. 25 | Blueprint 25: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 3 ore and 15 obsidian. 26 | Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 3 ore and 16 obsidian. 27 | Blueprint 27: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 4 ore and 16 obsidian. 28 | Blueprint 28: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 2 ore and 8 obsidian. 29 | Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 4 ore and 19 obsidian. 30 | Blueprint 30: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 10 clay. Each geode robot costs 2 ore and 7 obsidian. 31 | -------------------------------------------------------------------------------- /input/23/input.txt: -------------------------------------------------------------------------------- 1 | ####.##.#..#..#...#..#.#..#..#.##.##.####.#..#.#....###.....####..#.#..# 2 | .##.#..#...#...#....#...#..#.##....#.#..#.#..#.####.........#.#....####. 3 | #.#..#.#..#....###..#.#.#.....####.##....#.##...#.##..#..#..#..######.## 4 | .#.##......#.#..#...###..###.#....####..##..####.#..##.###..#..##.##.#.# 5 | #####.####.####.###.###.###..###...#.##......##...###.####.....##.#.##.. 6 | .....####.#..###.##.###.###.....###.#.##.#..##..##.#...#.#..#.##.#...#.# 7 | ###..#..#..#..#.#..#...#..#.##..#..#.....#....##.########..##.###.#....# 8 | ..#....###..##.####.#..#......##.#..####...#.....#..###..###......#.#... 9 | ####.....#..##....##...#..##..#.#####..####.####....##.####.#...###...#. 10 | ######.....##..#..#...#..###.#.#..#.##..#..#.....########..#....#.#.###. 11 | ##...####.###.####.##.###.#..###.#####....##..###......#....###...##...# 12 | ....#...#.#.##...#.#.###...##.#.#.#.##.#.##.###.#####.#.####.##.#####.#. 13 | .#.##.####.#.....#.##.##.#......#.#.#.##..#.##.####...#.#..#.#######.#.# 14 | #..#.#..##......#....#..##.#...####.#.#...#....#..####...#.#.##..#.###.# 15 | #.##..#.###..###..##.###..#..###.#....#.#.########..##..#.##.###.##..#.# 16 | .######.#.......#.##....#.###....##.##.#..##.#....#..##...#..#..#.##...# 17 | ...#.###..###.######...####.##..###.#...####.######.#.#.##..###.#.#..##. 18 | ....##....#...###..#...#.#.#.##.#..###.###...#..#..#.#.#..####...##...## 19 | .##.##.##..#.##......###....#.###.#...#..##.#.#####...############...... 20 | .#..#.#..###.####.#..#.##..###.###.##.#.#.#.##.###......#.##..###.#.#.## 21 | #..#....#.##..#.#.##.##.##....##..###.###..####..#.#.....#..#......##.## 22 | #...####...##..#....#.#.#.##.....#.......##...#....#.##....##..##...##.# 23 | ####.#.#..#.##.#######.###....###..#....#.##.####..##.#...#.####.#.#.#.. 24 | ............##..#..#.###...#.#.##....#######..#....#..###.....#.###...#. 25 | ##.##..##....####..#######.##.#..#....####....#.###.#.#...###....#.##.#. 26 | .##.#..#.##.###..##.#.#.#..#...##.#...#.##.#...#.###.#...#.##....##.#... 27 | .#.########.##.####.#.#...#....#.###..##.##.#.##.#..#.###......###..#..# 28 | ..#.#....#.#.#..##.#..###.#.###.##.##.##..#.#.#..##....#.##.#.....#.###. 29 | ..#...#.###..##..###.##..##.#..#....#.###...###...#..#.#.#.#..###.####.. 30 | ###...#.#..###...##.##.###....#.#......##..............#..##.#####.##.## 31 | ###..#.##.#.########.##.####..####...##.#.#.###...##.###.#.####.#.##.### 32 | ..##.###.#.#.#.##.#.####....#.####.#.##....###.###.##.##....#..##..##... 33 | ##..##.#.#......##..#.######...##..#...#.###..#.#.##...#....#.###.##.#.# 34 | ..####...##..#..#.#...#.#.###.##..#.#.#..##.##..#..##.#######...##...##. 35 | ....#..#..#.#...##.#.##.####..#..#..#..#...###..#.####.#.##.###.###..#.. 36 | .##...#.#.##.##..###.#.#..#.#.#.....##......##.#........#####.#......##. 37 | ###.####.#...##...#.#......####.#..#......##.....#..#####...#.###.##.#.# 38 | ...##.#..#.#.###....#####..#..###.#..#..#.#..#.#.#.#####.#..####..#..### 39 | .##.##....#.#.#.#..#######..#..#.#..#....#.####...#...#..##..#..#...#.## 40 | #.##.#.....#...#...###.##.##.##.#..#..#.###....##.#.#..##.##.###.##..##. 41 | #.##..#....########.#.#.....##.###.#..##.####.#.##..###.##...#.....#.#.# 42 | #..#..##.#....##...#.....#######.###########.#...####.#...####.#.#...##. 43 | ####.#..#..#...##..##.###.#.#.##..##.##.###.####..#.##.#.#....#..#.#.#.. 44 | #..#########...##..####..###..##.#..##....#..#.#.#..##.#..#.##.#.###.#.# 45 | #....#..#.##.##.....#####.#.......###..#.#.#...###..#...##.#..##.#..#.## 46 | #...####..#.#...#......#....#.#.#.#.##.....#.#.##..#...#.##........#.... 47 | ###........#.###..#....##.##......#..##..##...##.#.####...#.#..##..##.#. 48 | .#.#.##.#....##.#.#.#.#.#...##.#...##..#####.##.##.###..##..#.#...##...# 49 | ###.####...#......######.###..##..##.....###...#.......#..##.#..#.#.##.. 50 | ####.#..#.#.##.#####.#.#..#..####..##...#.###.##.##.##..#.###..#.#..###. 51 | #..###..#.#.###.#.#.##.#.##....#..#.#.###.##.#.##..###..#.##.....#..##.. 52 | .#..#.##.#####..#.....#....##......#.##.##.##....##.#...#.#.##.#..#..#.# 53 | ..#...##...#..####.##..#.##.#.##..#.##.#..####.#.#####......########..## 54 | .##.....##.##.######..#.##..#....#..#####.#.##..##..#.#.#.#..#...##..##. 55 | ##.#...###.##..##..#.#....#..#.#.#.#.##.#..#..#.#####.##.#####..##.#.#.. 56 | #.####.#..##..##...##..##.#.#......#.#####.#..####..#..########.##.###.. 57 | #.##.##.#.##...#..#..#.#...#.##.##..##......##.#####..##.#.#.#.##.##.#.# 58 | ..#....##....#..###.#####....##.#######..##..#####...#...#####.###.....# 59 | #.#.##..####.##.#......###..##.####.#...####.#..#.#.##.#..#.###.##.##### 60 | .##..####...###...#.##.#..#.##.######.#.##...#.##...##..##.##########... 61 | #.##.####..######...###.##.###.####.#.#..#..#.#.#..##....#......##....#. 62 | .###......##.##.####.#...##...#..#.#..####..#.#.##.###.#.....#.#....#.#. 63 | #..#..#.####.#.#.#..##..#..##.##..#.#..##.#########.###.######......#.## 64 | ..#.#.#.#..#######...#.#.....######.#.#.##.#..#...#..#####..##.###.#.##. 65 | ..#.#.#..##...####.#.###.##..#.#.##.#.#....##.#####.####...##..####....# 66 | ....#.##.....##...#.##.#.###..#.#...#####....##.###..#..########.....#.. 67 | #.#.#..#.#...###...##...#..#.##..#.#..#.....###.#####.##...#.#.#...##.#. 68 | ..#.#####..##....##...##.#.##.#..#..##.#...#.##..###..#.##...#..##..##.. 69 | #.###..##.#...####....###........#...###.#..##.#......###..####.#......# 70 | #..#.#.#....#...##.##.....#..##..#.####.##.....##.#.....#.#.#....##....# 71 | ########.#.##.#..##..#...####.#.###.#.#.#.##.#.######.##.#.##....####..# 72 | ###.#...##.###.##....#..##.##..#######..###.###...#..###.##...#...#..##. 73 | -------------------------------------------------------------------------------- /input/24/input.txt: -------------------------------------------------------------------------------- 1 | #.######################################################################################################################## 2 | #<^^^^v^><><^>^^>v^>^>^^v>>v^^^.v<>.<>v^^^^^^>>^^.^^>>vv><^v<^.<^^v^<<<># 3 | #<>>>^^v^>v>v>^^^^.<<<>><^^>>^^<>v^v^v>^v>v>^vv.vvv>v<^^<v^^><^>.>^^^.><^^.v^<<<>>>v^.^v.>>>^v.>.v># 4 | #.v^v>vv.v<<^v>^<^v<.v<<.>>^vv<.^v^<^^>^><^vv<^^^vvv<^.>>.^<^<.v<^<^v>><^^^><^>>v^<^^>>v^v<><^vv.>>^<^v>v^.><^^.>.^>v>^^>v><.^v<>^<>>^v.v.v..v^>><^^v>>^v^>^.>v^^>^v<^v^v<>.v>vvvv<^^.^<^v>v^>v<^<^^^<>^<^^.<^v^^<<>.>vvv^^^v^v^><^>v>><^.<^^^<>^v^v>^^v>>v>v<>v.^>>># 7 | #<^v>..>^>vv>v<.v>vv>v^v<<<<<<<>^>^^<>^^<>vv^.^><v<>>v.v.v^^>v<^v<^>v^v><<.<^^>v^vv<^^vv># 8 | #>v^^v^v>.<.^vv.^<<<<.>v>>v^<.<><>v><<<^.<>v><.v^<<.><<<^^vv>^>v<<<>^<.^v>vv^<<^.>^<^^v<<^>v>^.>>>.v^.v^vv<># 9 | #<<<>>...v^^>^v<^v>>><<<>>vv^^<^^>^v<.>v^.>>v<>>>^><.>v^.^vv>^v>^>>>vv^<<.^>>v>v^<>># 10 | #>.>>v<><^>>.^<>.>^v^.<^.<>^>v^.><<^>><>..v<>^^<^.>><<^<<^<.^>^.vv^^>^.^>^.v^>v<>^^vv<.<>>><<^v>v<<>>v><^^<># 11 | #<<>.v<<<>^.vv<^v>.>^<><>v^.v<>v<>>vv<<.^^<<>^>>.>^vvvv.vv>v>.>v.vv<^^^^v.vv^<..><# 12 | #>.<<vv>>^v>vvv<^<>.vvv^<^>v<>^^<.<^<><>^^^^<^>.>v>^>v>^<><v>v<^^v>v>><>^v<.^v^>v><..^.v<^# 13 | #>>^<^^^<>^<<>.<.vv<><^<>^>.>>v^^<^^^^>v^^^^vv<>>><<^^<<><<<>v^>>>^v>^><<<^^># 14 | #<^>.vv>>^.v.>^^v>.^<>>^<<..<^><><^v^^^>v<^^.<>v.<v^^<^<>>>vvv<^<^v>v>v^>..^.^>^>><>># 15 | #>^<><<>^<<^>v^.v><>.<<^>>.<^^.>v.>v>><^^<>vv<<<>v.v<<.vv<><>>^vv<>v>vv^.><.v^v><.v^.v^v>>v>>v<# 16 | #<<>><^^>.<>v>>^^<^v.<<<^v.^^<.>v^.^v^>v<>^v<<^<>vvv><>>v^^^>v<<<>.^^>v^v^^.><<.>>^^># 17 | #<^.v^>^v>^<^><<^v^>v^<><<^^^v<<^.v^v<>^.>.v<<<^^>v.>>.<>^.^v>v>v><^><<<>.^^<>v.<.v><><# 18 | #><<^>^^^^vv^>>.vv>vvv^v^>^^>v<<>^^^^>^>v<>>v^v>v><>.^vv^<>>>><<<>^>^.>..vv>^^^<^>v<>>^v>^vv>v>^^vv<><^>^^^..>>>># 19 | #>><<^v<<>^v>.^><^^v^<^<^v.><^>^^><^<<>>v<><.^<^>^v<<^^>.<>vv<^.# 20 | #<.^^>><>><>><><>^.^>.vv^>>><<>.^..>><<>.<>v<^>v<>vv^>vv<^<><<.vv><.<^^><>.vv<<>><>v# 21 | #v>>^<<.><^vv>>v>^^<^^^>v^^>><^v<^.<<^^v<^v>>^.^^v<^v^>v>>^.>v.>>v>.>^v<>vvvv><.>># 22 | #>v<<.^<^v<>v^^<>.v^>^v>>vv>^^>>>v<<<><<^^.>>^><>^>.v>^>v^^>^^>>>v>v^>>v>^^.v>.vvv>.v<>.^^<<<^vv^>^^># 23 | #>v^^.v^<.<^v<>.^<><>^^^.v.<<.^<^vv^>^v.^.v^>vvv<^<>^>^^v>v>v^.v^^>^>>^>^v^>^v^^.><^.^^>^^<^><>>^>.>.^^^v<<v>.# 24 | #<>v<^^<><.vv^><.v^.<>^<>.^<v.^v>.><^^<.>>vvv><^<^v<^v.^^><>>>.<^^^>v>>>><# 25 | #><^^v>>>vv>^<<<<>>v>vv>>>.v<<.<>>.<<^vv>^>^<<>>.v<^vv<^<>.vv^v^^<>>vvv<>^^^># 26 | #vvv^..v>^v^><^<^>>^.v^^<><>>v^>><><><^^^v>vv><<^<.>>>v<>v><>>^^<<.>>.><>vv>.^^vv>>>^vv^<^>^<^<^<<^># 27 | ########################################################################################################################.# 28 | -------------------------------------------------------------------------------- /input/25/input.txt: -------------------------------------------------------------------------------- 1 | 111-1==0 2 | 20=-10-1-11-2201== 3 | 21-20102 4 | 200=-121 5 | 21--1- 6 | 1-=-1 7 | 102-=-00- 8 | 2012=2-22=1 9 | 1-10==2- 10 | 1=-0-2- 11 | 1--122 12 | 1=210--00=-01-= 13 | 1-1111-= 14 | 2 15 | 20-01-= 16 | 2-2 17 | 11=0=- 18 | 1-= 19 | 111--202==1-0 20 | 2=1=-2 21 | 112-1102=1=-2= 22 | 2=-12==10-1 23 | 2-21 24 | 21 25 | 2=0-1210202=22 26 | 1=10=22=-2221==2=- 27 | 20- 28 | 1221=01 29 | 1-10- 30 | 210=10=020 31 | 1-20110-2=0-02-=110 32 | 1=002=2111=-= 33 | 112--2 34 | 2=0=11==-201 35 | 1-22=-1=-0-0 36 | 1--2-0-=1-0 37 | 1=-2-11=-=1--12-122 38 | 1-- 39 | 1=---1=212-10-1= 40 | 2-0==2-0-==0=-022 41 | 2120-2=0-2 42 | 12-=0= 43 | 1=0-20100= 44 | 1=-1=21-2-1=-20 45 | 1=2100=202112=1 46 | 102-2201=21210=10- 47 | 1-10=-=0-0-=-10-1 48 | 1-===--==11101 49 | 1-== 50 | 211-2 51 | 11=-=-121 52 | 12=-0 53 | 1121 54 | 210112222--1 55 | 1==2=01=2121021--== 56 | 1-=2 57 | 1=01=-212-0= 58 | 1-=10 59 | 1122112-1-21-2- 60 | 1==-002-1-0110=- 61 | 200--02 62 | 1-=20==2-2 63 | 20012001-00 64 | 22=12-122=11== 65 | 100-1122 66 | 1=-2-2-0201-2121011 67 | 2122-2111211-==-2 68 | 101-2--200=1-20 69 | 12-11 70 | 1=---01 71 | 122010-101 72 | 10-0022-201 73 | 1=002220- 74 | 10=2=02=212-- 75 | 1--20-0 76 | 1=0021=--=-=1-0-2101 77 | 22-22=0122=2=001 78 | 1==000211-=2==01=0 79 | 2===00 80 | 112=0-2=-2 81 | 12===1 82 | 1=2-1=1020=-0-=1 83 | 1=--12010= 84 | 1-=1- 85 | 22--000--120==01 86 | 20 87 | 1022120=12-=2 88 | 110=1 89 | 1-20200=1-20-100- 90 | 12--=12-01---21=1-- 91 | 1-1112=00=-=0222 92 | 20121--0=11-21= 93 | 102=01==1-121=-0 94 | 110 95 | 1=-2= 96 | 1=1 97 | 2-20-0-12- 98 | 2=-==0-- 99 | 10===2-1=11 100 | 11=1112-= 101 | 21201-0=010-20 102 | 1=011= 103 | 1==0=- 104 | 2=--0 105 | 2=--==02-- 106 | 1=21-==---=110=-1= 107 | 12-02010 108 | 1=--1211=-0 109 | 11012-2100 110 | 2=-0-=021--2-= 111 | 1=2=-01===0-==211 112 | 1=1-=-2 113 | 1010-2-01=0100 114 | 1-1--=--21=11011 115 | 11=-2=-112- 116 | 1-021=1-0-2 117 | 112 118 | 1=- 119 | 12 120 | 20-==-00-=2220=110 121 | 202220 122 | 22=-2-210010 123 | 1=0-222-2 124 | 12- 125 | 1=-020 126 | 12102-2=-20--=12 127 | 1-1201 128 | 22=-02-01 129 | 10=--1-0==2-= 130 | 1=2- 131 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2023-06-15" -------------------------------------------------------------------------------- /src/day01/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day01::Input; 2 | 3 | const INPUT: &str = include_str!("../../input/01/input.txt"); 4 | 5 | /// Read and parse the input file 6 | pub fn read() -> Input { 7 | // Iterate over each empty-line separated "chunk", 8 | // parsing each chunk into a total calorie count 9 | // per Elf, returning the list of total calories per 10 | // Elf. 11 | INPUT 12 | .trim() 13 | .split("\n\n") 14 | .map(try_parse_elf_calories) 15 | .collect::>() 16 | .expect("Could not parse input!") 17 | } 18 | 19 | /// Parse a "chunk" of lines representing an individual 20 | /// Elf's snacks into the total calories for that Elf. 21 | fn try_parse_elf_calories(value: &str) -> Result { 22 | // Iterate over each line, convert it to a u32, and sum the results 23 | let mut total = 0; 24 | for line in value.lines() { 25 | total += line.parse::()?; 26 | } 27 | Ok(total) 28 | } 29 | 30 | #[cfg(test)] 31 | mod test { 32 | use super::*; 33 | 34 | #[test] 35 | fn check_input() { 36 | let input = read(); 37 | 38 | let first = *input.first().unwrap(); 39 | assert_eq!(first, 44656); 40 | 41 | let last = *input.last().unwrap(); 42 | assert_eq!(last, 48165); 43 | 44 | assert_eq!(input.len(), 264); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/day01/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | 7 | // Input for today is a vector of numbers, each of which represents the 8 | // total number of calories carried by each Elf. In my first stab at this, 9 | // I used a wrapper around a `Vec>`, keeping track of each item. 10 | // Turns out, all I needed was the total calories per elf! 11 | pub type Input = Vec; 12 | 13 | pub fn run(part: Part) -> Output { 14 | let input = input::read(); 15 | match part { 16 | Part::One => part1::solve(&input), 17 | Part::Two => part2::solve(&input), 18 | } 19 | } 20 | 21 | pub fn run_both() -> (Output, Output) { 22 | let input = input::read(); 23 | (part1::solve(&input), part2::solve(&input)) 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn check_answer_one() { 32 | let result = run(Part::One); 33 | assert_eq!(result, 69795); 34 | } 35 | 36 | #[test] 37 | fn check_answer_two() { 38 | let result = run(Part::Two); 39 | assert_eq!(result, 208437); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/day01/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day01::{Input, Output}; 2 | 3 | /// Solve Day 01, Part 01 4 | pub fn solve(input: &Input) -> Output { 5 | // Get the maximum calorie count for an Elf 6 | // and return it as an Output::U32. 7 | input.iter().copied().max().unwrap_or_default().into() 8 | } 9 | -------------------------------------------------------------------------------- /src/day01/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day01::{Input, Output}; 2 | 3 | /// Solve Day 01, Part 02 4 | pub fn solve(input: &Input) -> Output { 5 | // A running total of the top three values for total 6 | // Elf calories found so far, descending from left to 7 | // right. 8 | let mut top_three = [u32::MIN; 3]; 9 | 10 | // For each Elf's total snack calories... 11 | for calories in input.iter() { 12 | // Need a local (mutable) copy of the calories value 13 | let mut calories = *calories; 14 | 15 | // For each value in the current top three... 16 | for top_value in top_three.iter_mut() { 17 | // If the current Elf's total calories are greater than 18 | // the current top value, then swap the values. `calories` 19 | // now contains the previous `top_value` and the previous 20 | // value for `calories` is in `top_three`. Repeat for all 21 | // values in `top_three` until `top_three` contains the 22 | // largest three values of `top_three` and `calories`. 23 | if calories > *top_value { 24 | std::mem::swap(top_value, &mut calories); 25 | } 26 | } 27 | } 28 | 29 | // Return the sum of the top three calorie counts 30 | top_three.iter().sum::().into() 31 | } 32 | -------------------------------------------------------------------------------- /src/day02/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day02::Input; 2 | use nom::{ 3 | character::complete::{anychar, space1}, 4 | error::Error as NomError, 5 | sequence::separated_pair, 6 | Finish, IResult, 7 | }; 8 | 9 | const INPUT: &str = include_str!("../../input/02/input.txt"); 10 | 11 | /// Attempts to parse a line from the INPUT 12 | fn parse_line(line: &str) -> Result<(char, char), NomError<&str>> { 13 | // parses lines that contain a character, one or more spaces, 14 | // then another character 15 | let (_, char_pair) = separated_pair(anychar, space1, anychar)(line).finish()?; 16 | Ok(char_pair) 17 | } 18 | 19 | /// Parse the INPUT 20 | pub fn read() -> Input { 21 | // Parse the lines into (char, char) values and return the resulting 22 | // list. Ignores any lines that fail to parse. 23 | INPUT.lines().flat_map(parse_line).collect() 24 | } 25 | 26 | #[cfg(test)] 27 | mod test { 28 | use super::*; 29 | 30 | #[test] 31 | fn check_input_parsing() { 32 | let input = read(); 33 | 34 | let first_round = *input.first().expect("There should be at least one round!"); 35 | let first_expected = ('B', 'X'); 36 | assert_eq!(first_round, first_expected); 37 | 38 | let last_round = *input.last().expect("There should be at least one round!"); 39 | let last_expected = ('B', 'Y'); 40 | assert_eq!(last_round, last_expected); 41 | 42 | assert_eq!(input.len(), 2500); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/day02/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | pub mod shared; 5 | 6 | use crate::{Output, Part}; 7 | 8 | // Today's input is a list of character pairs 9 | pub type Input = Vec<(char, char)>; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 10994); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 12526); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day02/part1.rs: -------------------------------------------------------------------------------- 1 | use super::shared::{Outcome, Shape}; 2 | use crate::day02::{Input, Output}; 3 | 4 | /// Solve part one 5 | pub fn solve(input: &Input) -> Output { 6 | // For each pair of characters in the input, convert each to an `Outcome`, 7 | // calculate the score of that `Outcome`, and return the total as an `Output`. 8 | input 9 | .iter() 10 | .flat_map(|pair| pair.try_into_outcome()) 11 | .map(|outcome| outcome.score()) 12 | .sum::() 13 | .into() 14 | } 15 | 16 | /// Trait for converting a character from the input into a `Shape`. 17 | /// I'm using a Trait here so that I can use the same function names 18 | /// in the two different parts but have them behave differently, while 19 | /// sharing some base functionality between parts. 20 | trait TryIntoShape { 21 | type Error; 22 | fn try_into_shape(&self) -> Result; 23 | } 24 | 25 | impl TryIntoShape for char { 26 | type Error = &'static str; 27 | 28 | /// Attempt to convert an input character into a `Shape`. Yes, we know 29 | /// that there will be no other characters, but I like to practice good 30 | /// input hygiene when I can. 31 | fn try_into_shape(&self) -> Result { 32 | match self { 33 | 'A' | 'X' => Ok(Shape::Rock), 34 | 'B' | 'Y' => Ok(Shape::Paper), 35 | 'C' | 'Z' => Ok(Shape::Scissors), 36 | _ => Err("Character cannot be converted to `Shape`!"), 37 | } 38 | } 39 | } 40 | 41 | /// Trait for converting a pair of characters from the input into an `Outcome`. 42 | /// Same deal as the other trait above. 43 | trait TryIntoOutcome { 44 | type Error; 45 | fn try_into_outcome(&self) -> Result; 46 | } 47 | 48 | impl TryIntoOutcome for (char, char) { 49 | type Error = &'static str; 50 | 51 | #[rustfmt::skip] // I _like_ my pretty match statement below 52 | fn try_into_outcome(&self) -> Result { 53 | // Attempt to convert both characters into their respective `Shape` 54 | let (ch1, ch2) = self; 55 | let opponent = ch1.try_into_shape()?; 56 | let player = ch2.try_into_shape()?; 57 | 58 | // Based on the shapes, determine who won and return the `Outcome` 59 | use Shape::*; 60 | let result = match (opponent, player) { 61 | (Rock, Rock) => Outcome::Draw(player), 62 | (Rock, Paper) => Outcome::Win(player), 63 | (Rock, Scissors) => Outcome::Lose(player), 64 | (Paper, Rock) => Outcome::Lose(player), 65 | (Paper, Paper) => Outcome::Draw(player), 66 | (Paper, Scissors) => Outcome::Win(player), 67 | (Scissors, Rock) => Outcome::Win(player), 68 | (Scissors, Paper) => Outcome::Lose(player), 69 | (Scissors, Scissors) => Outcome::Draw(player), 70 | }; 71 | Ok(result) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/day02/part2.rs: -------------------------------------------------------------------------------- 1 | use super::shared::{Outcome, Shape}; 2 | use crate::day02::{Input, Output}; 3 | 4 | /// Solve part two 5 | pub fn solve(input: &Input) -> Output { 6 | // For each pair of characters in the input, convert each to an `Outcome`, 7 | // calculate the score of that `Outcome`, and return the total as an `Output`. 8 | input 9 | .iter() 10 | .flat_map(|pair| pair.try_into_outcome()) 11 | .map(|outcome| outcome.score()) 12 | .sum::() 13 | .into() 14 | } 15 | 16 | /// Trait for converting a character from the input into a `Shape`. 17 | /// I'm using a Trait here so that I can use the same function names 18 | /// in the two different parts but have them behave differently, while 19 | /// sharing some base functionality between parts. 20 | trait TryIntoShape { 21 | type Error; 22 | fn try_into_shape(&self) -> Result; 23 | } 24 | 25 | impl TryIntoShape for char { 26 | type Error = &'static str; 27 | 28 | /// Attempt to convert an input character into a `Shape`. This time, 29 | /// we know that 'X', 'Y', and 'Z' do not represent shapes, so we don't 30 | /// try to convert them. 31 | fn try_into_shape(&self) -> Result { 32 | match self { 33 | 'A' => Ok(Shape::Rock), 34 | 'B' => Ok(Shape::Paper), 35 | 'C' => Ok(Shape::Scissors), 36 | _ => Err("Character cannot be converted to `Shape`!"), 37 | } 38 | } 39 | } 40 | 41 | /// Trait for converting a pair of characters from the input into an `Outcome`. 42 | /// Same deal as the other trait above. 43 | trait TryIntoOutcome { 44 | type Error; 45 | fn try_into_outcome(&self) -> Result; 46 | } 47 | 48 | impl TryIntoOutcome for (char, char) { 49 | type Error = &'static str; 50 | 51 | #[rustfmt::skip] // I _still like_ my pretty match statement below 52 | fn try_into_outcome(&self) -> Result { 53 | // Now, we only convert the first character into a `Shape` 54 | let (ch1, result) = self; 55 | let opponent = ch1.try_into_shape()?; 56 | 57 | // Using the mapping that 'X' means we lose, 'Y' means we draw, and 58 | // 'Z' means we win, determine the outcome of the game and what shape 59 | // you the player need to make to achieve that outcome, and return 60 | // the `Outcome`. 61 | use Shape::*; 62 | match (opponent, result) { 63 | (Rock, 'Y') => Ok(Outcome::Draw(Rock)), 64 | (Rock, 'Z') => Ok(Outcome::Win(Paper)), 65 | (Rock, 'X') => Ok(Outcome::Lose(Scissors)), 66 | (Paper, 'X') => Ok(Outcome::Lose(Rock)), 67 | (Paper, 'Y') => Ok(Outcome::Draw(Paper)), 68 | (Paper, 'Z') => Ok(Outcome::Win(Scissors)), 69 | (Scissors, 'Z') => Ok(Outcome::Win(Rock)), 70 | (Scissors, 'X') => Ok(Outcome::Lose(Paper)), 71 | (Scissors, 'Y') => Ok(Outcome::Draw(Scissors)), 72 | (_, _) => Err("Cannot convert character pair to an Outcome!"), 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/day02/shared.rs: -------------------------------------------------------------------------------- 1 | //! Common structs and functions for both parts. `Shape` and `Outcome` 2 | //! are used in both parts and the functionality for converting them into 3 | //! points is unchanged between the parts. 4 | 5 | /// Represents a 'shape' in a game of Rock, Paper, Scissors 6 | #[derive(Clone, Copy)] 7 | pub enum Shape { 8 | Rock, 9 | Paper, 10 | Scissors, 11 | } 12 | 13 | /// Convert a `Shape` into it's score value 14 | impl From for u32 { 15 | fn from(shape: Shape) -> Self { 16 | match shape { 17 | Shape::Rock => 1, 18 | Shape::Paper => 2, 19 | Shape::Scissors => 3, 20 | } 21 | } 22 | } 23 | 24 | /// Represents the outcome of a game of Rock, Paper, Scissors, from the 25 | /// perspective of you, the player. Each variant encapsulates the shape 26 | /// you made to achieve that outcome. 27 | pub enum Outcome { 28 | Win(Shape), 29 | Draw(Shape), 30 | Lose(Shape), 31 | } 32 | 33 | impl Outcome { 34 | /// Calculate the score from a given outcome 35 | pub fn score(&self) -> u32 { 36 | match self { 37 | // 6 points for winning + the points for your shape 38 | Outcome::Win(t) => 6 + u32::from(*t), 39 | 40 | // 3 points for a draw + the points for your shape 41 | Outcome::Draw(t) => 3 + u32::from(*t), 42 | 43 | // 0 points for losing + the points for your shape 44 | Outcome::Lose(t) => u32::from(*t), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/day03/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day03::Input; 2 | use anyhow::{anyhow, bail, Error, Result}; 3 | 4 | const INPUT: &str = include_str!("../../input/03/input.txt"); 5 | 6 | // Today we'll do a bit of math converting ASCII characters to numbers. 7 | // These constants are used in that math. For references, ASCII 'a' corresponds 8 | // to a value of 97, and ASCII 'A' corresponds to a value of 65. 9 | const LOWERCASE_OFFSET: u32 = 96; // 'a' -> 97 - 1 (priority of 'a') = 96 10 | const CAPITAL_OFFSET: u32 = 38; // 'A' -> 65 - 26 (priority of 'A') = 38 11 | 12 | /// A `Rucksack` represents a pack carried by an elf with two different, 13 | /// separated sets of `Item`s. 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 | pub struct Rucksack(pub ItemSet, pub ItemSet); 16 | 17 | /// Attempt to convert a line from the input into a `Rucksack` 18 | impl TryFrom<&str> for Rucksack { 19 | type Error = Error; 20 | 21 | fn try_from(s: &str) -> Result { 22 | // Because the input lines contain two equal-length strings of 23 | // characters representing `Item`s, we need the length of each 24 | // half. 25 | let compartment_len = s.len() / 2; 26 | 27 | // Split the line into two equal-length strings 28 | let (str1, str2) = s.split_at(compartment_len); 29 | 30 | // Convert each string into a set of `Item`s 31 | let compartment1 = ItemSet::try_from(str1)?; 32 | let compartment2 = ItemSet::try_from(str2)?; 33 | 34 | // Return a `Rucksack` with two sets of items 35 | Ok(Rucksack(compartment1, compartment2)) 36 | } 37 | } 38 | 39 | /// An `ItemSet` represents the unique items held in each compartment of a 40 | /// `Rucksack`. The goal is to provide functionality equivalent to a 41 | /// `HashSet` (at least as much as we need) without the overhead 42 | /// of an actual `HashSet`. This is accomplished by assigning each type of 43 | /// `Item` to a particular bit in the inner `u64`. 44 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 45 | pub struct ItemSet(pub u64); 46 | 47 | impl ItemSet { 48 | /// Insert an `Item` into an `ItemSet`. 49 | fn insert(&mut self, item: Item) { 50 | // Set the bit in the `ItemSet` that corresponds to the particular type 51 | // of `Item`. 52 | self.0 |= item.0; 53 | } 54 | } 55 | 56 | /// Attempt to convert a string slice into a set of `Item`s. 57 | impl TryFrom<&str> for ItemSet { 58 | type Error = Error; 59 | 60 | fn try_from(value: &str) -> Result { 61 | // Start with an empty item set 62 | let mut item_set = ItemSet::default(); 63 | 64 | // Convert each character in the input string into an `Item` and 65 | // insert each `Item` into the set 66 | for ch in value.chars() { 67 | let item = Item::try_from(ch)?; 68 | item_set.insert(item); 69 | } 70 | 71 | Ok(item_set) 72 | } 73 | } 74 | 75 | /// An `Item` represents a particular item carried by the elves. Because the puzzle 76 | /// specifies that there are only 52 possible item types, each item can be uniquely 77 | /// represented by a single set bit in a `u64` with 6 extra bits to spare. We'll set 78 | /// bits in order of increasing priority, starting with 'a' at 2^1. This way, the 79 | /// number of trailing zeros will be equal to priority. So, 'a' will be stored as 80 | /// 2u64 with 1 trailing zero, and 'A' will be stored as 134217728u64 with 27 trailing 81 | /// zeros. 82 | /// 83 | /// Just for excessive clarity, 134217728u64 is represented in bits as: 84 | /// 0b00000000000000000000000000000000001000000000000000000000000000 85 | #[derive(Debug, Clone, Copy)] 86 | pub struct Item(pub u64); 87 | 88 | /// Attempt to convert a single character into an `Item` 89 | impl TryFrom for Item { 90 | type Error = Error; 91 | 92 | #[rustfmt::skip] 93 | fn try_from(value: char) -> Result { 94 | // Error if trying to make an `Item` out of any character that's not a letter 95 | if !value.is_alphabetic() { bail!("Cannot convert {value} to an Item!") } 96 | 97 | // Offset for ASCII range starts and priority offset. 98 | let offset = if value > 'Z' { LOWERCASE_OFFSET } else { CAPITAL_OFFSET }; 99 | let priority = value as u32 - offset; 100 | let set_bit = 1 << priority; // One bit, shifted left by priority 101 | 102 | Ok(Item(set_bit)) 103 | } 104 | } 105 | 106 | /// Read and parse the input 107 | pub fn read() -> Input { 108 | // Attempt to convert each line into a `Rucksack` and return the 109 | // list of successful results. 110 | INPUT.lines().flat_map(Rucksack::try_from).collect() 111 | } 112 | 113 | #[cfg(test)] 114 | mod test { 115 | use super::*; 116 | 117 | #[test] 118 | fn check_input() { 119 | let input = read(); 120 | assert_eq!(input.len(), 300); 121 | 122 | let first = *input.first().unwrap(); 123 | let first_expected = Rucksack::try_from("VdzVHmNpdVmBBCpmQLTNfTtMhMJnhFhTTf").unwrap(); 124 | assert_eq!(first, first_expected); 125 | 126 | let last = *input.last().unwrap(); 127 | let last_expected = Rucksack::try_from("vjWPWjWPPPWgwmfCrNvTvZ").unwrap(); 128 | assert_eq!(last, last_expected); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/day03/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{Item, ItemSet, Rucksack}; 7 | 8 | pub type Input = Vec; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 8153); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 2342); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day03/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day03::{Input, Item, ItemSet, Output, Rucksack}; 2 | use anyhow::{bail, Error, Result}; 3 | 4 | /// Solve Day 3, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | // For each `Rucksack`, identify the one item in common between the 7 | // compartments, calculate that item's priority, and return the sum 8 | // of all unique item priorities. 9 | input 10 | .iter() 11 | .flat_map(|r| r.one_in_common()) 12 | .map(|i| i.priority()) 13 | .sum::() 14 | .into() 15 | } 16 | 17 | impl Rucksack { 18 | /// Attempt to identify the one `Item` in common between both compartments. 19 | fn one_in_common(&self) -> Result { 20 | self.0.intersect(self.1).try_into() 21 | } 22 | } 23 | 24 | impl ItemSet { 25 | /// Construct an `ItemSet` that contains only items in common between 26 | /// `self` and `other`. Just a bitwise _and_ on the underlying integers. 27 | pub fn intersect(&self, other: ItemSet) -> ItemSet { 28 | ItemSet(self.0 & other.0) 29 | } 30 | } 31 | 32 | /// Attempt to convert an `ItemSet` into a single `Item`. 33 | /// Fails and returns an Error if the `ItemSet` contains more than one item. 34 | impl TryFrom for Item { 35 | type Error = Error; 36 | 37 | fn try_from(set: ItemSet) -> Result { 38 | if set.0.count_ones() > 1 { 39 | bail!("{set:?} contains more than one item!") 40 | } 41 | Ok(Item(set.0)) 42 | } 43 | } 44 | 45 | impl Item { 46 | /// Calculate the priority of the `Item`. Recall each `Item` is represented 47 | /// by a single bit shifted left by priority, so priority is just the 48 | /// number of trailing zeros. 49 | pub fn priority(&self) -> u32 { 50 | self.0.trailing_zeros() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/day03/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day03::{Input, Item, ItemSet, Output, Rucksack}; 2 | use anyhow::Result; 3 | use itertools::Itertools; 4 | 5 | pub fn solve(input: &Input) -> Output { 6 | let mut total = 0; // The sum of badge priorities 7 | 8 | // Convert each `Rucksack` to an `ItemSet` consisting of all its unique 9 | // `Item`s, as an iterator. 10 | let rucksack_sets = input.iter().map(|r| r.all_items()); 11 | 12 | // For each chunk of three rucksack_sets in sequence... 13 | for chunk in &rucksack_sets.chunks(3) { 14 | // Produce an `ItemSet` that is the intersection of all three sets 15 | // in the chunk. If the chunk is empty, we get an empty `ItemSet` back. 16 | let intersect = chunk 17 | .reduce(|acc, i| acc.intersect(i)) 18 | .unwrap_or(ItemSet(0)); 19 | 20 | // Attempt to convert the `ItemSet` into a single Item. Panic if 21 | // the chunk contains more than one common item. The puzzle text 22 | // assures us this won't happen. 23 | let badge = Item::try_from(intersect).expect("{intersect:?} contains multiple Items!"); 24 | 25 | // Add the priority of the badge to the total 26 | total += badge.priority(); 27 | } 28 | 29 | total.into() 30 | } 31 | 32 | impl Rucksack { 33 | /// Return an `ItemSet` comprised of the items in both compartments of 34 | /// the `Rucksack`. Recall that this is a set, so duplicates aren't counted. 35 | fn all_items(&self) -> ItemSet { 36 | ItemSet(self.0 .0 | self.1 .0) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day04/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day04::Input; 2 | 3 | /// Represents a range of beach assignments for a particular elf 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 5 | pub struct AssignmentRange { 6 | pub start: u8, 7 | pub stop: u8, 8 | } 9 | 10 | /// Convert a pair of integers to an `AssignmentRange` 11 | impl From<(u8, u8)> for AssignmentRange { 12 | fn from(value: (u8, u8)) -> Self { 13 | let (start, stop) = value; 14 | AssignmentRange { start, stop } 15 | } 16 | } 17 | 18 | /// Represents a pair of elf beach cleaning assignment ranges 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] 20 | pub struct AssignmentRangePair(pub AssignmentRange, pub AssignmentRange); 21 | 22 | /// Convert a pair of assignment ranges into an `AssignmentRangePair` 23 | impl From<(AssignmentRange, AssignmentRange)> for AssignmentRangePair { 24 | fn from(value: (AssignmentRange, AssignmentRange)) -> Self { 25 | let (first, second) = value; 26 | AssignmentRangePair(first, second) 27 | } 28 | } 29 | 30 | /// I find that I like having the `nom` parser functions bundled under a module 31 | /// like this. It helps to namespace the parser combinators by usage and keeps 32 | /// me from polluting the parent module with a bunch of otherwise ambiguously 33 | /// named functions. Another option I'm playing with is bundling them as impl 34 | /// functions under an empty struct. Not sure which I like better yet, but this has 35 | /// the advantage of allowing me to include the `use::nom::*` imports close to where 36 | /// the parser combinators are defined. I'll need to try it out in a few more 37 | /// situations to get a better handle on the pros and cons. 38 | mod arp_parser { 39 | use super::{AssignmentRange, AssignmentRangePair}; 40 | use nom::{ 41 | bytes::complete::tag, character::complete::u8, combinator::into, error::Error as NomError, 42 | sequence::separated_pair, Finish, IResult, 43 | }; 44 | 45 | /// Nom parser for "12-22" -> (12u8, 22u8) 46 | fn number_pair(s: &str) -> IResult<&str, (u8, u8)> { 47 | separated_pair(u8, tag("-"), u8)(s) 48 | } 49 | 50 | /// Nom parser for "12-22" -> AssignmentRange { start: 12, stop: 22 } 51 | fn range(s: &str) -> IResult<&str, AssignmentRange> { 52 | into(number_pair)(s) 53 | } 54 | 55 | /// Nom parser for "12-22,18-24" -> AssignmentRangePair( 56 | /// AssignmentRange { start: 12, stop: 22 }, 57 | /// AssignmentRange { start: 18, stop: 24 }, 58 | /// ) 59 | pub fn parse(s: &str) -> Result> { 60 | let pair_parser = separated_pair(range, tag(","), range); 61 | let parse_result = into(pair_parser)(s); 62 | let (_, ranges) = parse_result.finish()?; 63 | Ok(ranges) 64 | } 65 | } 66 | 67 | // Keep the input file as a compile time constant string slice 68 | const INPUT: &str = include_str!("../../input/04/input.txt"); 69 | 70 | /// Read the input by parsing each line into an `AssignmentRangePair` and discarding 71 | /// any lines that return an Error. We'll check in the tests to make sure every line 72 | /// is parsed. 73 | pub fn read() -> Input { 74 | INPUT.lines().flat_map(arp_parser::parse).collect() 75 | } 76 | 77 | #[cfg(test)] 78 | mod test { 79 | use super::*; 80 | 81 | #[test] 82 | fn check_input() { 83 | let input = read(); 84 | assert_eq!(input.len(), 1000); 85 | 86 | let first = *input.first().unwrap(); 87 | let first_expected = AssignmentRangePair::from(( 88 | AssignmentRange::from((14, 50)), 89 | AssignmentRange::from((14, 50)), 90 | )); 91 | assert_eq!(first, first_expected); 92 | 93 | let last = *input.last().unwrap(); 94 | let last_expected = AssignmentRangePair::from(( 95 | AssignmentRange::from((98, 98)), 96 | AssignmentRange::from((17, 99)), 97 | )); 98 | assert_eq!(last, last_expected); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/day04/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::pedantic)] 2 | pub mod input; 3 | pub mod part1; 4 | pub mod part2; 5 | 6 | use crate::{Output, Part}; 7 | use input::{AssignmentRange, AssignmentRangePair}; 8 | 9 | pub type Input = Vec; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 540); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 872); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day04/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day04::{AssignmentRange, AssignmentRangePair, Input, Output}; 2 | 3 | /// Solve Day 4, Part 1 4 | pub fn solve(input: &Input) -> Output { 5 | // For each assignment pair in the input, check to see if one assignment 6 | // complete contains another. Count only the pairs where this check returns 7 | // true. 8 | let result = input 9 | .iter() 10 | .map(|pair| pair.full_containment()) 11 | .filter(|x| *x) 12 | .count() as u32; 13 | result.into() 14 | } 15 | 16 | /// Trait for determine whether one `AssignmentRange` completely contains another 17 | trait RangeContains { 18 | fn contains(&self, other: &Self) -> bool; 19 | } 20 | 21 | impl RangeContains for AssignmentRange { 22 | fn contains(&self, other: &Self) -> bool { 23 | // Return true if one range is completely inside another 24 | self.start <= other.start && self.stop >= other.stop 25 | } 26 | } 27 | 28 | impl AssignmentRangePair { 29 | /// Return true if either range in the pair completely contains the other one. 30 | fn full_containment(&self) -> bool { 31 | let AssignmentRangePair(first, second) = self; 32 | first.contains(second) || second.contains(first) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/day04/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day04::{AssignmentRange, AssignmentRangePair, Input, Output}; 2 | 3 | /// Solve Day 4, Part 2 4 | pub fn solve(input: &Input) -> Output { 5 | let result = input 6 | .iter() 7 | .map(|pair| pair.ranges_overlap()) 8 | .filter(|x| *x) 9 | .count() as u32; 10 | result.into() 11 | } 12 | 13 | /// Trait for determine whether one `AssignmentRange` overlaps another 14 | trait RangeOverlap { 15 | fn overlaps(&self, other: &Self) -> bool; 16 | } 17 | 18 | impl RangeOverlap for AssignmentRange { 19 | fn overlaps(&self, other: &Self) -> bool { 20 | // Return true if one range overlaps another 21 | self.start <= other.stop && self.stop >= other.start 22 | } 23 | } 24 | 25 | impl AssignmentRangePair { 26 | /// Return true if either range in the pair overlaps another 27 | fn ranges_overlap(&self) -> bool { 28 | let AssignmentRangePair(first, second) = self; 29 | first.overlaps(second) || second.overlaps(first) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/day05/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{CrateStack, CrateStacks, Instruction}; 7 | 8 | pub type Input = (CrateStacks, Vec); 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, "ZWHVFWQWW"); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, "HZFZCCWWV"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day05/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day05::{CrateStack, CrateStacks, Input, Instruction, Output}; 2 | 3 | /// Solve Day 05, Part 1 4 | pub fn solve(input: &Input) -> Output { 5 | // Split up the input into the stacks of crates and the instructions, 6 | // then clone the crate stacks struct so we can have a mutable copy. 7 | let (crate_stacks, instructions) = input; 8 | let mut crate_stacks = crate_stacks.clone(); 9 | 10 | // Execute each instruction on the `CrateStacks`, and return the 11 | // String resulting from the top crate in each stack 12 | instructions.iter().for_each(|i| crate_stacks.execute(i)); 13 | crate_stacks.message().into() 14 | } 15 | 16 | /// Implement methods for popping crates from the top of a stack and 17 | /// pushing new crates to the top of a stack. 18 | impl CrateStack { 19 | /// To push a new crate to the `CrateStack`, just add it to the index past the 20 | /// last crate (just _happens_ to be `height`) and bump up the height. 21 | pub fn push(&mut self, ch: char) { 22 | self.crates[self.height] = ch; 23 | self.height += 1; 24 | } 25 | 26 | /// To pop a crate from the `CrateStack`, bump the height down and return the 27 | /// character from the top of the stack. Note, this doesn't actually _remove_ 28 | /// the character from the top of the stack, but the next crate pushed to 29 | /// this stack will overwrite it. So long as we stick to pushing and popping, 30 | /// we won't have any issues. 31 | pub fn pop(&mut self) -> char { 32 | if self.height == 0 { 33 | return '.'; 34 | } 35 | self.height -= 1; 36 | self.crates[self.height] 37 | } 38 | } 39 | 40 | impl CrateStacks { 41 | /// Convenience method to pop from one stack out of all the stacks. Which 42 | /// stack to pop from is given by `crate_idx` as indicated by an `Instruction`. 43 | pub fn pop_from(&mut self, crate_idx: u8) -> char { 44 | if !(1..=9).contains(&crate_idx) { 45 | return '.'; 46 | } 47 | self[(crate_idx as usize)].borrow_mut().pop() 48 | } 49 | 50 | /// Convenience method to push to one stack out of all the stacks. Which 51 | /// stack to push to is given by `crate_idx` as indicated by an `Instruction`. 52 | pub fn push_to(&mut self, crate_idx: u8, ch: char) { 53 | if !(1..=9).contains(&crate_idx) { 54 | return; 55 | } 56 | self[(crate_idx as usize)].borrow_mut().push(ch); 57 | } 58 | 59 | /// Fetches the top character (crate) from each stack and builds a String 60 | /// out of them. 61 | pub fn message(&mut self) -> String { 62 | let mut out = String::new(); 63 | for stack in self.0.iter_mut() { 64 | let top_crate = stack.borrow_mut().pop(); 65 | out.push(top_crate); 66 | } 67 | out 68 | } 69 | } 70 | 71 | /// This is the trait for executing instructions on a `CrateStacks`. Using a trait 72 | /// here so to allow for different functionality between parts one and two. 73 | trait Execute { 74 | fn execute(&mut self, _: &Instruction); 75 | } 76 | 77 | impl Execute for CrateStacks { 78 | /// Really just boils down to pop crates from one stack and push them onto 79 | /// another, as many times as the `Instruction` says to. 80 | fn execute(&mut self, instruction: &Instruction) { 81 | let Instruction { 82 | count, 83 | origin, 84 | destination, 85 | } = instruction; 86 | for _ in 0..*count { 87 | let top_crate = self.pop_from(*origin); 88 | self.push_to(*destination, top_crate); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/day05/part2.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeFrom; 2 | 3 | use crate::day05::{CrateStack, CrateStacks, Input, Instruction, Output}; 4 | 5 | /// Solve Day 05, Part 2 6 | pub fn solve(input: &Input) -> Output { 7 | // Split up the input into the stacks of crates and the instructions, 8 | // then clone the crate stacks struct so we can have a mutable copy. 9 | let (crate_stacks, instructions) = input; 10 | let mut crate_stacks = crate_stacks.clone(); 11 | 12 | // Execute each instruction on the `CrateStacks`, and return the 13 | // String resulting from the top crate in each stack 14 | instructions.iter().for_each(|i| crate_stacks.execute(i)); 15 | crate_stacks.message().into() 16 | } 17 | 18 | impl CrateStack { 19 | /// Move multiple crates from one stack to another without changing their order. 20 | /// We do this by setting the new height of the stack to remove crates from, and 21 | /// taking crates from that stack starting with that new height and moving them 22 | /// to the other stack in a "bottoms-up" approach. 23 | fn transfer_many(&mut self, other: &mut Self, n: u8) { 24 | self.height -= n as usize; 25 | for offset in 0..(n as usize) { 26 | let mut crate_to_move = &mut self.crates[self.height + offset]; 27 | let mut crate_destination = &mut other.crates[other.height + offset]; 28 | std::mem::swap(crate_to_move, crate_destination); 29 | } 30 | other.height += n as usize; 31 | } 32 | } 33 | 34 | impl CrateStacks { 35 | /// Convenience function to specify transferring many crates from one stack 36 | /// to another. Essentially takes all the parameters from an `Instruction` 37 | /// to do it. 38 | pub fn transfer_many_between(&mut self, origin: u8, destination: u8, n: u8) { 39 | let mut origin_stack = self[origin as usize].borrow_mut(); 40 | let mut destination_stack = self[destination as usize].borrow_mut(); 41 | origin_stack.transfer_many(&mut destination_stack, n); 42 | } 43 | } 44 | 45 | /// This is the trait for executing instructions on a `CrateStacks`. Using a trait 46 | /// here so to allow for different functionality between parts one and two. 47 | trait Execute { 48 | fn execute(&mut self, _: &Instruction); 49 | } 50 | 51 | impl Execute for CrateStacks { 52 | /// Not much happening in this function other than unpacking the `Instruction` 53 | /// and passing its parameters to `CrateStacks.transfer_many_between()`. 54 | fn execute(&mut self, instruction: &Instruction) { 55 | let Instruction { 56 | count, 57 | origin, 58 | destination, 59 | } = instruction; 60 | self.transfer_many_between(*origin, *destination, *count); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/day06/input.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Error}; 2 | 3 | use crate::day06::Input; 4 | 5 | const INPUT: &str = include_str!("../../input/06/input.txt"); 6 | 7 | /// Read in the input by converting each character to a `Signal` and returning 8 | /// the list. 9 | pub fn read() -> Input { 10 | Box::new(INPUT.chars().flat_map(Signal::try_from)) 11 | } 12 | 13 | /// Represents a single signal received on our device. Each character from the 14 | /// input string can be represented as a `u32` with a single set bit, offset 15 | /// from the left in alphabetic order. For example: 16 | /// 17 | /// 'a' -> Signal(00000000000000000000000000000001) 18 | /// 'b' -> Signal(00000000000000000000000000000010) 19 | /// 'x' -> Signal(00000000100000000000000000000000) 20 | /// 'z' -> Signal(00000010000000000000000000000000) 21 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 22 | pub struct Signal(pub u32); 23 | 24 | impl TryFrom for Signal { 25 | type Error = Error; 26 | 27 | fn try_from(value: char) -> Result { 28 | /// Anything besides a lowercase letter cannot make a Signal 29 | if !value.is_ascii_lowercase() { 30 | bail!("Cannot convert {value} to a `Signal`!"); 31 | } 32 | 33 | /// Set a single bit and shift it left by the letter offset, return 34 | /// as a Signal 35 | let shift = (value as u32) - 97; 36 | Ok(Signal(1 << shift)) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod test { 42 | use super::*; 43 | 44 | #[test] 45 | fn name() { 46 | let input: Vec<_> = read().collect(); 47 | assert_eq!(input.len(), 4095); 48 | 49 | let first_signal = *input.first().unwrap(); 50 | let first_expected = Signal::try_from('d').unwrap(); 51 | assert_eq!(first_signal, first_expected); 52 | 53 | let last_signal = *input.last().unwrap(); 54 | let last_expected = Signal::try_from('j').unwrap(); 55 | assert_eq!(last_signal, last_expected); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/day06/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | pub mod shared; 5 | 6 | use crate::{Output, Part}; 7 | use input::Signal; 8 | use shared::SequenceDetector; 9 | 10 | pub type Input = Box>; 11 | 12 | pub fn run(part: Part) -> Output { 13 | let input = input::read(); 14 | match part { 15 | Part::One => part1::solve(input::read()), 16 | Part::Two => part2::solve(input::read()), 17 | } 18 | } 19 | 20 | pub fn run_both() -> (Output, Output) { 21 | (part1::solve(input::read()), part2::solve(input::read())) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 1647); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 2447); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day06/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day06::{Input, Output, SequenceDetector}; 2 | 3 | pub fn solve(input: Input) -> Output { 4 | let mut detector: SequenceDetector<4> = SequenceDetector::new(); 5 | for (idx, signal) in input.enumerate() { 6 | if detector.detect(signal) { 7 | return (idx as u32 + 1).into(); 8 | } 9 | } 10 | panic!("No start-of-packet marker detected!") 11 | } 12 | -------------------------------------------------------------------------------- /src/day06/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day06::{Input, Output, SequenceDetector}; 2 | 3 | /// Solve Day 6, Part 2 4 | pub fn solve(input: Input) -> Output { 5 | // Instantiate a detector for sequences of length 14 6 | let mut detector: SequenceDetector<14> = SequenceDetector::new(); 7 | 8 | // Pass each `Signal` in the input to the detector. Return early 9 | // with the index (plus one) if a unique sequence is detected. 10 | for (idx, signal) in input.enumerate() { 11 | if detector.detect(signal) { 12 | return (idx as u32 + 1).into(); 13 | } 14 | } 15 | panic!("No start-of-message marker detected!") 16 | } 17 | -------------------------------------------------------------------------------- /src/day06/shared.rs: -------------------------------------------------------------------------------- 1 | use super::input::Signal; 2 | use std::ops::{BitAnd, BitOrAssign, BitXorAssign}; 3 | 4 | /// Implement bitwise _and_ for `Signal`s 5 | impl BitAnd for Signal { 6 | type Output = Signal; 7 | 8 | fn bitand(self, rhs: Self) -> Self::Output { 9 | Signal(self.0 & rhs.0) 10 | } 11 | } 12 | 13 | /// Implement bitwise _or-assign_ for `Signal`s 14 | impl BitOrAssign for Signal { 15 | fn bitor_assign(&mut self, rhs: Self) { 16 | self.0 |= rhs.0; 17 | } 18 | } 19 | 20 | impl BitXorAssign for Signal { 21 | fn bitxor_assign(&mut self, rhs: Self) { 22 | self.0 ^= rhs.0 23 | } 24 | } 25 | 26 | impl Signal { 27 | fn count_components(&self) -> u32 { 28 | self.0.count_ones() 29 | } 30 | } 31 | 32 | /// Represents a 'detector' for unique sequences of `Signal`s of a constant length. 33 | /// Holds a buffer of the signals encountered so far and inserts new signals into 34 | /// that buffer by wrapping around the length of the buffer. 35 | #[derive(Debug)] 36 | pub struct SequenceDetector { 37 | buffer: [Signal; N], 38 | mark: usize, 39 | composite_signal: Signal, 40 | } 41 | 42 | impl SequenceDetector { 43 | /// Create a new `SequenceDetector` with an empty buffer and a marker set to the 44 | /// first index of the buffer. 45 | pub fn new() -> Self { 46 | let buffer = [Signal::default(); N]; 47 | let mark = 0; 48 | let composite_signal = Signal::default(); 49 | Self { buffer, mark, composite_signal } 50 | } 51 | 52 | /// Given a `Signal`, indicates whether the most recent `N` signals detected 53 | /// comprises a unique sequence. 54 | pub fn detect(&mut self, signal: Signal) -> bool { 55 | // Remove the oldest signal from the composite signal, add the current 56 | // signal to the buffer and the composite signal, and bump the marker over 57 | // by one to receive the next signal. 58 | self.composite_signal ^= self.buffer[self.mark]; 59 | 60 | if self.composite_signal.count_components() < 3 { 61 | self.composite_signal |= self.buffer[self.mark]; 62 | } 63 | 64 | self.composite_signal |= signal; 65 | self.buffer[self.mark] = signal; 66 | self.mark = (self.mark + 1) % N; 67 | 68 | // Return an indicator as to whether or not the buffer contains N unique 69 | // signals, indicated by the number of signals in the composite signal 70 | self.composite_signal.count_components() == N as u32 71 | } 72 | } 73 | 74 | impl Default for SequenceDetector { 75 | fn default() -> Self { 76 | Self::new() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/day07/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{DirRef, FileSystem, FileSystemObj}; 7 | 8 | pub type Input<'a> = FileSystem<'a>; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 1367870); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 549173); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day07/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day07::{DirRef, FileSystem, FileSystemObj, Input, Output}; 2 | 3 | /// Solve Day 7, Part 1 4 | pub fn solve(input: &Input) -> Output { 5 | // From the list of directory sizes, keep only the sizes less than 100_000 6 | // and return the total of those directory sizes. 7 | input 8 | .get_directory_sizes() 9 | .iter() 10 | .filter(|x| **x <= 100_000) 11 | .sum::() 12 | .into() 13 | } 14 | 15 | impl FileSystem<'_> { 16 | /// Pull the sizes of each directory in the file system into a vector 17 | pub fn get_directory_sizes(&self) -> Vec { 18 | let mut sizes = Vec::new(); 19 | 20 | // Walk the file system tree structure, adding directory sizes to `sizes` 21 | fn walk(dir: DirRef, sizes: &mut Vec) { 22 | sizes.push(dir.borrow().size); 23 | for item in dir.borrow().dirs.iter() { 24 | walk(item.clone(), sizes); 25 | } 26 | } 27 | 28 | // Do the walk! 29 | walk(self.0.clone(), &mut sizes); 30 | sizes 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/day07/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day07::{FileSystem, FileSystemObj, Input, Output}; 2 | 3 | /// Solve Day 7, Part 2 4 | pub fn solve(input: &Input) -> Output { 5 | // Calculate the space we need to free up as described by the puzzle 6 | let space_remaining = 70_000_000 - input.total_size(); 7 | let space_needed = 30_000_000 - space_remaining; 8 | 9 | // Iterate through the directory sizes and find the ones whose size is at least 10 | // as large as the amount of space we need, and take the smallest size of those. 11 | input 12 | .get_directory_sizes() 13 | .into_iter() 14 | .filter(|x| *x >= space_needed) 15 | .min() 16 | .unwrap() 17 | .into() 18 | } 19 | 20 | impl FileSystem<'_> { 21 | /// Get the size of the root directory, which is the total size of the files 22 | /// in our file system. 23 | fn total_size(&self) -> u32 { 24 | self.0.borrow().size 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/day08/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day08::Input; 2 | 3 | /// Represents our overall view of the trees. Really just a two-dimensional vector 4 | /// of the input characters, converted to numbers, with the number of rows and 5 | /// columns brought along for the ride. 6 | #[derive(Debug)] 7 | pub struct TreeView { 8 | pub row_len: usize, 9 | pub col_len: usize, 10 | pub trees: Vec>, 11 | } 12 | 13 | /// Parser a TreeView from the input file. No parser combinators today, since they're 14 | /// almost entirely unnecessary for this one. 15 | impl From<&str> for TreeView { 16 | fn from(input: &str) -> Self { 17 | let row_len = input.lines().count(); 18 | let col_len = input.lines().next().unwrap().len(); 19 | let line_to_nums = |s: &str| s.chars().map(|c| c as u8 - b'0').collect(); 20 | let trees: Vec> = input.lines().map(line_to_nums).collect(); 21 | 22 | TreeView { 23 | row_len, 24 | col_len, 25 | trees, 26 | } 27 | } 28 | } 29 | 30 | const INPUT: &str = include_str!("../../input/08/input.txt"); 31 | 32 | /// Read the input file and convert it to a `TreeView` 33 | pub fn read() -> Input { 34 | TreeView::from(INPUT) 35 | } 36 | -------------------------------------------------------------------------------- /src/day08/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::TreeView; 7 | 8 | pub type Input = TreeView; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 1801); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 209880) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day08/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day08::{Input, Output}; 2 | 3 | /// Solve Day 8, Part 1 4 | #[allow(clippy::needless_range_loop)] 5 | pub fn solve(input: &Input) -> Output { 6 | // Start with a two-dimensional vector the same size as the input, 7 | // filled with `false`. So far, we can't see any trees! 8 | let mut visibility_map = vec![vec![false; input.col_len]; input.row_len]; 9 | 10 | // For each row of trees... 11 | for row_idx in 0..input.row_len { 12 | // The tallest tree found so far has a height of 0 13 | let mut tallest = 0; 14 | 15 | // For each column from left to right... 16 | for col_idx in 0..input.col_len { 17 | // Take the tree at that position 18 | let tree = input.trees[row_idx][col_idx]; 19 | 20 | // If it's taller than the current `tallest` OR it's on the left 21 | // or right edge, then set that tree to visible and update the 22 | // tallest found so far. 23 | if tree > tallest || col_idx == 0 || col_idx == (input.col_len - 1) { 24 | visibility_map[row_idx][col_idx] = true; 25 | tallest = tree; 26 | } 27 | } 28 | 29 | // Reset `tallest` and do the same thing again from right to left. There's 30 | // no need to attend to the edges again on this round. 31 | tallest = 0; 32 | for col_idx in 0..input.col_len { 33 | let tree = input.trees[row_idx][col_idx]; 34 | if tree > tallest { 35 | visibility_map[row_idx][col_idx] = true; 36 | tallest = tree; 37 | } 38 | } 39 | } 40 | 41 | // Now scan the columns from top to bottom and bottom to top 42 | for col_idx in 0..input.col_len { 43 | // Same deal, except this time we'll also trigger the visibility update 44 | // on the top and bottom rows, as well. 45 | let mut tallest = 0; 46 | for row_idx in 0..input.row_len { 47 | let tree = input.trees[row_idx][col_idx]; 48 | if tree > tallest || row_idx == 0 || row_idx == (input.row_len - 1) { 49 | visibility_map[row_idx][col_idx] = true; 50 | tallest = tree; 51 | } 52 | } 53 | 54 | // If you guess "reset the tallest height and scan from bottom to top", 55 | // the you guessed right! 56 | tallest = 0; 57 | for row_idx in (0..input.row_len).rev() { 58 | let tree = input.trees[row_idx][col_idx]; 59 | if tree > tallest { 60 | visibility_map[row_idx][col_idx] = true; 61 | tallest = tree; 62 | } 63 | } 64 | } 65 | 66 | // Count the number of visible trees in the visibility map and return the count 67 | let found = visibility_map 68 | .iter() 69 | .flat_map(|row| row.iter()) 70 | .filter(|x| **x) 71 | .count() as u32; 72 | found.into() 73 | } 74 | -------------------------------------------------------------------------------- /src/day08/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day08::{Input, Output}; 2 | use std::cmp::{max, min}; 3 | 4 | /// Solve Day 8, Part 2 5 | pub fn solve(input: &Input) -> Output { 6 | // This time, we'll instantiate a 2D vector the same size and shape as our 7 | // view of the trees, just filled with zeros! 8 | let mut scenic_score_map = vec![vec![0u32; input.col_len]; input.row_len]; 9 | 10 | // Pre-computing a list of all the row/column indices so we only have 11 | // one `for` loop below instead of two. Keeps the nesting down. 12 | let indices = (0..input.row_len).flat_map(|r| (0..input.col_len).map(move |c| (r, c))); 13 | 14 | // For each tree location in the forest... 15 | for (row_idx, col_idx) in indices { 16 | // If we're on an edge of the map, just skip it. It'll be zero regardless. 17 | if row_idx == 0 18 | || col_idx == 0 19 | || row_idx == (input.row_len - 1) 20 | || col_idx == (input.col_len - 1) 21 | { 22 | continue; 23 | } 24 | 25 | // The tree at our current location 26 | let tree = input.trees[row_idx][col_idx]; 27 | 28 | // From our tree, loop up and count the number of trees visible. We do this 29 | // by iterating over the positions in the same column and in rows above our 30 | // current tree until we either reach the edge or hit a tree our own height. 31 | let mut can_see_up = 0; 32 | for seek_idx in (0..row_idx).rev() { 33 | let found = input.trees[seek_idx][col_idx]; 34 | can_see_up += 1; 35 | if found >= tree { 36 | break; 37 | } 38 | } 39 | 40 | // Same deal, just looking down. 41 | let mut can_see_down = 0; 42 | for seek_idx in (row_idx + 1)..input.row_len { 43 | let found = input.trees[seek_idx][col_idx]; 44 | can_see_down += 1; 45 | if found >= tree { 46 | break; 47 | } 48 | } 49 | 50 | // Same deal, just looking left 51 | let mut can_see_left = 0; 52 | for seek_idx in (0..col_idx).rev() { 53 | let found = input.trees[row_idx][seek_idx]; 54 | can_see_left += 1; 55 | if found >= tree { 56 | break; 57 | } 58 | } 59 | 60 | // Same deal, just looking right 61 | let mut can_see_right = 0; 62 | for seek_idx in (col_idx + 1)..input.col_len { 63 | let found = input.trees[row_idx][seek_idx]; 64 | can_see_right += 1; 65 | if found >= tree { 66 | break; 67 | } 68 | } 69 | 70 | // Calculate the scenic score of this tree as the product of all the 71 | // counts from looking up, down, left, and right 72 | scenic_score_map[row_idx][col_idx] = 73 | can_see_up * can_see_down * can_see_left * can_see_right; 74 | } 75 | 76 | // Now we just loop over the 2D map and return the largest score found 77 | scenic_score_map 78 | .iter() 79 | .flat_map(|row| row.iter()) 80 | .copied() 81 | .max() 82 | .unwrap() 83 | .into() 84 | } 85 | -------------------------------------------------------------------------------- /src/day09/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day09::Input; 2 | 3 | /// Represents one of the motions specified in the input, either up, 4 | /// down, left, or right by a given distance (or number of steps). 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 | pub enum Motion { 7 | Up(u8), 8 | Down(u8), 9 | Left(u8), 10 | Right(u8), 11 | } 12 | 13 | /// Module wrapping the parser for today's puzzle. Produces a `Vec`. 14 | mod parser { 15 | use super::*; 16 | use anyhow::{anyhow, Result}; 17 | use nom::{ 18 | branch::alt, bytes::complete::tag, character::complete::u8, combinator::map, 19 | multi::separated_list1, sequence::preceded, Finish, IResult, 20 | }; 21 | 22 | /// Nom parser for "U 5" -> Motion::Up(5) 23 | fn up(s: &str) -> IResult<&str, Motion> { 24 | map(preceded(tag("U "), u8), Motion::Up)(s) 25 | } 26 | 27 | /// Nom parser for "D 5" -> Motion::Down(5) 28 | fn down(s: &str) -> IResult<&str, Motion> { 29 | map(preceded(tag("D "), u8), Motion::Down)(s) 30 | } 31 | 32 | /// Nom parser for "L 5" -> Motion::Left(5) 33 | fn left(s: &str) -> IResult<&str, Motion> { 34 | map(preceded(tag("L "), u8), Motion::Left)(s) 35 | } 36 | 37 | /// Nom parser for "R 5" -> Motion::Right(5) 38 | fn right(s: &str) -> IResult<&str, Motion> { 39 | map(preceded(tag("R "), u8), Motion::Right)(s) 40 | } 41 | 42 | /// Nom parser to take all the lines of the input and produce a vector 43 | /// of `Motion`s 44 | pub fn parse(s: &str) -> Result> { 45 | let result = separated_list1(tag("\n"), alt((up, down, left, right)))(s); 46 | let (_, motions) = result 47 | .finish() 48 | .map_err(|_| anyhow!("Could not parse motions!"))?; 49 | Ok(motions) 50 | } 51 | } 52 | 53 | const INPUT: &str = include_str!("../../input/09/input.txt"); 54 | 55 | pub fn read() -> Input { 56 | parser::parse(INPUT).unwrap() 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use super::*; 62 | 63 | #[test] 64 | fn check_input() { 65 | let input = read(); 66 | assert_eq!(input.len(), 2000); 67 | 68 | let first_found = *input.first().unwrap(); 69 | let first_expected = Motion::Down(2); 70 | assert_eq!(first_found, first_expected); 71 | 72 | let last_found = *input.last().unwrap(); 73 | let last_expected = Motion::Right(3); 74 | assert_eq!(last_found, last_expected); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/day09/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | pub mod shared; 5 | 6 | use crate::{Output, Part}; 7 | use input::Motion; 8 | use shared::Knot; 9 | 10 | pub type Input = Vec; 11 | 12 | pub fn run(part: Part) -> Output { 13 | let input = input::read(); 14 | match part { 15 | Part::One => part1::solve(&input), 16 | Part::Two => part2::solve(&input), 17 | } 18 | } 19 | 20 | pub fn run_both() -> (Output, Output) { 21 | let input = input::read(); 22 | (part1::solve(&input), part2::solve(&input)) 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn check_answer_one() { 31 | let result = run(Part::One); 32 | assert_eq!(result, 6175); 33 | } 34 | 35 | #[test] 36 | fn check_answer_two() { 37 | let result = run(Part::Two); 38 | assert_eq!(result, 2578); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/day09/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day09::{Input, Knot, Motion, Output}; 2 | use std::collections::HashSet; 3 | use std::ops::AddAssign; 4 | 5 | pub fn solve(input: &Input) -> Output { 6 | // Brand new `RopeSimulator`(tm) 7 | let mut simulator = RopeSimulator::new(); 8 | 9 | // For each specified motion, update the simulator 10 | input.iter().for_each(|motion| simulator.move_head(motion)); 11 | 12 | // Return the number of unique tail positions from the simulator 13 | let unique_tail_pos = simulator.hist.len() as u32; 14 | unique_tail_pos.into() 15 | } 16 | 17 | /// A struct to encapsulate the state of the rope, with a HashSet to keep up with 18 | /// the unique positions of the tail. 19 | pub struct RopeSimulator { 20 | head: Knot, 21 | tail: Knot, 22 | hist: HashSet, 23 | } 24 | 25 | impl RopeSimulator { 26 | /// Create a new RopeSimulator with head and tail both at the origin (0, 0) and 27 | /// initializing the set of tail positions to contain the initial tail position. 28 | fn new() -> Self { 29 | let head = Knot::default(); 30 | let tail = Knot::default(); 31 | let hist = HashSet::from([Knot::default()]); 32 | RopeSimulator { head, tail, hist } 33 | } 34 | 35 | /// Move the tail of the rope towards the head according to the rules given by 36 | /// the puzzle instructions. 37 | fn move_tail(&mut self) { 38 | let Knot(hx, hy) = self.head; 39 | let Knot(tx, ty) = self.tail; 40 | 41 | // This `use` statement means we don't have to fully quality all the 42 | // different `Ordering` variants below. Makes it cleaner to look at. 43 | use std::cmp::Ordering::*; 44 | 45 | // Set the position of the tail based on where the head is relative to it. 46 | // For example, if the head is positioned diagonally up and to the left of 47 | // the tail, then we'll match on `(Less, Less)` and move the tail up and 48 | // to the left. 49 | self.tail = match (hx.cmp(&tx), hy.cmp(&ty)) { 50 | (Less, Less) => Knot(tx - 1, ty - 1), 51 | (Less, Equal) => Knot(tx - 1, ty), 52 | (Less, Greater) => Knot(tx - 1, ty + 1), 53 | (Equal, Less) => Knot(tx, ty - 1), 54 | (Equal, Equal) => unreachable!(), 55 | (Equal, Greater) => Knot(tx, ty + 1), 56 | (Greater, Less) => Knot(tx + 1, ty - 1), 57 | (Greater, Equal) => Knot(tx + 1, ty), 58 | (Greater, Greater) => Knot(tx + 1, ty + 1), 59 | }; 60 | 61 | // Add the new tail position to the set of tracked tail positions. 62 | self.hist.insert(self.tail); 63 | } 64 | 65 | fn move_head(&mut self, motion: &Motion) { 66 | // Generate a specification for moving the head. We get the number of 67 | // steps from the `Motion`, and the offset indicates how the `Position` 68 | // of the head is changed on each step. 69 | let (steps, offset) = match motion { 70 | Motion::Up(steps) => (steps, (0, -1)), 71 | Motion::Down(steps) => (steps, (0, 1)), 72 | Motion::Left(steps) => (steps, (-1, 0)), 73 | Motion::Right(steps) => (steps, (1, 0)), 74 | }; 75 | 76 | // Now we move the head one step at a time, adjusting the head by 77 | // the offset each time. Any time the head is too far from the 78 | // tail, we adjust the tail and record its new position. 79 | for _ in 0..*steps { 80 | self.head += offset; 81 | if self.head.too_far(&self.tail) { 82 | self.move_tail(); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/day09/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day09::{Input, Knot, Motion, Output}; 2 | use itertools::Itertools; 3 | use std::collections::HashSet; 4 | use std::ops::AddAssign; 5 | 6 | pub fn solve(input: &Input) -> Output { 7 | // Brand new `RopeSimulator`(tm) 8 | let mut simulator: RopeSimulator<10> = RopeSimulator::new(); 9 | 10 | // For each specified motion, update the simulator 11 | input.iter().for_each(|motion| simulator.move_head(motion)); 12 | 13 | // Return the number of unique tail positions from the simulator 14 | let unique_tail_pos = simulator.hist.len() as u32; 15 | unique_tail_pos.into() 16 | } 17 | 18 | // New and improved! I realize we only _needed_ to support a rope with 10 19 | // knots, but at this point, why not make it *const generic*? 20 | pub struct RopeSimulator { 21 | knots: [Knot; N], 22 | hist: HashSet, 23 | } 24 | 25 | impl RopeSimulator { 26 | // New RopeSimulator, this time we're keeping all the knots in an array 27 | // of length N (10 for our case). Note that the order of these knots 28 | // matters, since `knots[0]` will be the head and `knots[N - 1]` will be 29 | // the tail. 30 | fn new() -> Self { 31 | let knots = [Knot::default(); N]; 32 | let hist = HashSet::from([Knot::default()]); 33 | RopeSimulator { knots, hist } 34 | } 35 | 36 | // This time, instead of hard-coding the head and the tail, we pass in 37 | // the index of the `leader` knot and the `follower` knot. For our 38 | // implementation, follower == leader + 1; 39 | fn follow(&mut self, leader: usize, follower: usize) { 40 | let Knot(hx, hy) = self.knots[leader]; 41 | let Knot(tx, ty) = self.knots[follower]; 42 | 43 | // The logic here is exactly the same as for the first part 44 | use std::cmp::Ordering::*; 45 | self.knots[follower] = match (hx.cmp(&tx), hy.cmp(&ty)) { 46 | (Less, Less) => Knot(tx - 1, ty - 1), 47 | (Less, Equal) => Knot(tx - 1, ty), 48 | (Less, Greater) => Knot(tx - 1, ty + 1), 49 | (Equal, Less) => Knot(tx, ty - 1), 50 | (Equal, Equal) => unreachable!(), 51 | (Equal, Greater) => Knot(tx, ty + 1), 52 | (Greater, Less) => Knot(tx + 1, ty - 1), 53 | (Greater, Equal) => Knot(tx + 1, ty), 54 | (Greater, Greater) => Knot(tx + 1, ty + 1), 55 | }; 56 | 57 | // Now we need to check to be sure the `follower` is in the tail 58 | // slot before we record its position. 59 | if follower == N - 1 { 60 | self.hist.insert(self.knots[N - 1]); 61 | } 62 | } 63 | 64 | fn move_head(&mut self, motion: &Motion) { 65 | // Generate a specification for moving the head. We get the number of 66 | // steps from the `Motion`, and the offset indicates how the `Knot` 67 | // of the head is changed on each step. 68 | let (reps, offset) = match motion { 69 | Motion::Up(reps) => (reps, (0, -1)), 70 | Motion::Down(reps) => (reps, (0, 1)), 71 | Motion::Left(reps) => (reps, (-1, 0)), 72 | Motion::Right(reps) => (reps, (1, 0)), 73 | }; 74 | 75 | // For each step in the motion, move the first knot in the `knots` array 76 | // (that's the head), then move down the array of knots, updating each 77 | // knot in sequence based on the position of the previous knot. 78 | for _ in 0..*reps { 79 | self.knots[0] += offset; 80 | 81 | // Note the `tuple_windows()` method from the `itertools` crate. Handy 82 | // crate, that `itertools`. 83 | for (leader, follower) in (0..N).tuple_windows() { 84 | // If the first knot is too far away from the knot behind it, move 85 | // the follower. 86 | if self.knots[leader].too_far(&self.knots[follower]) { 87 | self.follow(leader, follower); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/day09/shared.rs: -------------------------------------------------------------------------------- 1 | use std::ops::AddAssign; 2 | 3 | /// Represents the position of a knot in x/y space. 4 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] 5 | pub struct Knot(pub i32, pub i32); 6 | 7 | /// Check if the current knot is "too far" from another knot. This means 8 | /// it's more than one unit away in either dimension. 9 | impl Knot { 10 | pub fn too_far(&self, other: &Knot) -> bool { 11 | let Knot(x1, y1) = self; 12 | let Knot(x2, y2) = other; 13 | x1.abs_diff(*x2) > 1 || y1.abs_diff(*y2) > 1 14 | } 15 | } 16 | 17 | /// You know what's nice? Adding an offset to a knot to 'move' the position! 18 | impl AddAssign<(i32, i32)> for Knot { 19 | fn add_assign(&mut self, rhs: (i32, i32)) { 20 | let Knot(x, y) = self; 21 | let (xd, yd) = rhs; 22 | *self = Knot(*x + xd, *y + yd); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/day10/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day10::Input; 2 | 3 | /// Represents an instruction to our handheld device. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub enum Instruction { 6 | Noop, 7 | Addx(i32), 8 | } 9 | 10 | /// Module wrapping the parser for the instructions from the input. 11 | mod parser { 12 | use super::*; 13 | use anyhow::{anyhow, Result}; 14 | use nom::{ 15 | branch::alt, 16 | bytes::complete::tag, 17 | character::complete::i32, 18 | combinator::{map, value}, 19 | multi::separated_list1, 20 | sequence::preceded, 21 | Finish, IResult, 22 | }; 23 | 24 | /// Nom parser for "noop" -> Instruction:Noop 25 | fn noop(s: &str) -> IResult<&str, Instruction> { 26 | value(Instruction::Noop, tag("noop"))(s) 27 | } 28 | 29 | /// Nom parser for "addx 3" -> Instruction::Addx(3) 30 | fn addx(s: &str) -> IResult<&str, Instruction> { 31 | map(preceded(tag("addx "), i32), Instruction::Addx)(s) 32 | } 33 | 34 | /// Nom parser for either instruction variant 35 | fn instruction(s: &str) -> IResult<&str, Instruction> { 36 | alt((noop, addx))(s) 37 | } 38 | 39 | /// Nom parser for all instruction lines -> Vec 40 | pub fn parse(s: &str) -> Result> { 41 | let result = separated_list1(tag("\n"), instruction)(s); 42 | let (_, instrs) = result.finish().map_err(|e| anyhow!("{e}"))?; 43 | Ok(instrs) 44 | } 45 | } 46 | 47 | const INPUT: &str = include_str!("../../input/10/input.txt"); 48 | 49 | /// Parse that input! 50 | pub fn read() -> Input { 51 | parser::parse(INPUT).unwrap() 52 | } 53 | 54 | #[cfg(test)] 55 | mod test { 56 | use super::*; 57 | 58 | #[test] 59 | fn check_input() { 60 | let input = read(); 61 | assert_eq!(input.len(), 145); 62 | 63 | let third_found = *input.get(2).unwrap(); 64 | let third_expected = Instruction::Addx(5); 65 | assert_eq!(third_found, third_expected); 66 | 67 | let near_last_found = *input.get(141).unwrap(); 68 | let near_last_expected = Instruction::Addx(2); 69 | assert_eq!(near_last_found, near_last_expected); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/day10/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::Instruction; 7 | 8 | pub type Input = Vec; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 17020); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | let expected = ""; 37 | let expected = "### # #### #### #### # ## #### \n\ 38 | # # # # # # # # # # \n\ 39 | # # # ### # ### # # ### \n\ 40 | ### # # # # # # ## # \n\ 41 | # # # # # # # # # # \n\ 42 | # # #### #### #### # #### ### #### "; 43 | assert_eq!(result, expected); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/day10/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day10::{Input, Instruction, Output}; 2 | 3 | /// Solve Day 10, Part 1 4 | pub fn solve(input: &Input) -> Output { 5 | // Instantiate a new device, run all the instructions on it, then return 6 | // the total signal strength from that device. 7 | let mut device = Device::new(); 8 | input 9 | .iter() 10 | .for_each(|instruction| device.execute(instruction)); 11 | device.signal_strength.into() 12 | } 13 | 14 | /// Represents our handheld computing device, complete with signal strength 15 | /// accumulator! 16 | struct Device { 17 | register: i32, 18 | cycle: usize, 19 | signal_strength: i32, 20 | } 21 | 22 | impl Device { 23 | fn new() -> Self { 24 | Device { 25 | register: 1, 26 | cycle: 1, 27 | signal_strength: 0, 28 | } 29 | } 30 | 31 | // Execute a NOOP instruction. We'll leverage these instructions to update the 32 | // signal strength based on the current cycle count. 33 | fn execute_noop(&mut self) { 34 | self.cycle += 1; 35 | let multiple_of_20 = self.cycle % 20 == 0; 36 | let odd_multiple = (self.cycle / 20) % 2 == 1; 37 | 38 | // We'll update the signal strength on cycles 20, 60, 100, 140, etc. 39 | // These are all odd multiples of 20, that is, (20 * 1), (20 * 3), (20 * 5), etc. 40 | // So, we check that the current cycle count is a multiple of 20 and that the 41 | // current cycle count divided by 20 is an odd number. 42 | if multiple_of_20 && odd_multiple { 43 | self.signal_strength += (self.cycle as i32) * self.register; 44 | } 45 | } 46 | 47 | /// Execute an ADDX instruction. We leverage the NOOP instructions here to update 48 | /// the cycle count and the signal strength, if necessary. It's important that 49 | /// the register be updated between the NOOP instructions, since the puzzle states 50 | /// signal strength is checked _during_ the cycle and not after. 51 | fn execute_addx(&mut self, value: i32) { 52 | self.execute_noop(); 53 | self.register += value; 54 | self.execute_noop(); 55 | } 56 | 57 | /// Dispatch for instruction execution. Calls the appropriate execute method 58 | /// depending on the instruction provided. 59 | fn execute(&mut self, instr: &Instruction) { 60 | match instr { 61 | Instruction::Noop => self.execute_noop(), 62 | Instruction::Addx(v) => self.execute_addx(*v), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/day10/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day10::{Input, Instruction, Output}; 2 | use std::fmt::{Display, Formatter, Result as FmtResult}; 3 | 4 | /// Solve Day 10, Part 2 5 | pub fn solve(input: &Input) -> Output { 6 | // Boot up a new model of device and run all the instructions on it. 7 | let mut device = Device::new(); 8 | input 9 | .iter() 10 | .for_each(|instruction| device.execute(instruction)); 11 | 12 | // Collect the prettified pixel display into a string and return it 13 | PrettyPixels(device.pixels).to_string().into() 14 | } 15 | 16 | /// Represents a new-fangled computer with a display. We'll keep track of pixels 17 | /// lit in a boolean array and worry about displaying them later. 18 | struct Device { 19 | register: i32, 20 | cycle: usize, 21 | pixels: [bool; 240], 22 | } 23 | 24 | impl Device { 25 | fn new() -> Self { 26 | Device { 27 | register: 1, 28 | cycle: 0, 29 | pixels: [false; 240], 30 | } 31 | } 32 | 33 | // Execute a NOOP instruction. We'll leverage these instructions to update the 34 | // pixels based on the current sprite position. 35 | fn execute_noop(&mut self) { 36 | // Calculate the three-wide position of the sprite. 37 | let sprite_range = (self.register - 1)..=(self.register + 1); 38 | 39 | // The current line position is the cycle wrapped to 40-width lines. So, on 40 | // cycle 40, we've wrapped around to the first pixel of the second line. 41 | let line_pos = (self.cycle % 40) as i32; 42 | 43 | // If the current line position is within the sprite, set the corresponding 44 | // pixel in our array of pixels to `true`. 45 | if sprite_range.contains(&line_pos) { 46 | self.pixels[self.cycle] = true; 47 | } 48 | 49 | self.cycle += 1; 50 | } 51 | 52 | /// Execute an ADDX instruction. Once again, we leverage the NOOP instructions here 53 | /// to update the cycle count and the pixels. This time, we need to update the 54 | /// register _after_ both NOOPs, since pixel drawing happens at the _beginning_ 55 | /// of each cycle. 56 | fn execute_addx(&mut self, value: i32) { 57 | self.execute_noop(); 58 | self.execute_noop(); 59 | self.register += value; 60 | } 61 | 62 | /// Dispatch for instruction execution. Calls the appropriate execute method 63 | /// depending on the instruction provided. 64 | fn execute(&mut self, instr: &Instruction) { 65 | match instr { 66 | Instruction::Noop => self.execute_noop(), 67 | Instruction::Addx(v) => self.execute_addx(*v), 68 | } 69 | } 70 | } 71 | 72 | /// This is how we worry about displaying pixels. Using the newtype syntax, we can 73 | /// implement a standard library trait on a (wrapped) standard library data structure. 74 | /// In this case, we want to implement `Display` for our array of pixels so we can 75 | /// convert it to a string that can show us the answer to part two. 76 | struct PrettyPixels([bool; 240]); 77 | 78 | #[rustfmt::skip] 79 | impl Display for PrettyPixels { 80 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 81 | for (idx, pixel) in self.0.iter().enumerate() { 82 | // Wrap the pixel lines to a width of 40 characters 83 | if (idx % 40 == 0) && idx > 0 { writeln!(f)?; } 84 | 85 | // If the pixel is lit, print a '#', other wise print a space 86 | let glyph = if *pixel { "#" } else { " " }; 87 | write!(f, "{glyph}")?; 88 | } 89 | 90 | write!(f, "") // Finish the print results 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/day11/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | pub mod shared; 5 | 6 | use crate::{Output, Part}; 7 | use input::{Monkey, Operation, Rule}; 8 | 9 | pub type Input = Vec; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 56350u64); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 13954061248u64); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day11/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day11::{Input, Monkey, Output}; 2 | use itertools::Itertools; 3 | 4 | /// Solve Day 11, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | // Initiate a cruel, cruel game played by monkeys 7 | let mut monkey_game: CruelGame = CruelGame::from(input); 8 | 9 | // "Play" the game for 20 rounds 10 | (0..20).for_each(|_| monkey_game.play()); 11 | 12 | // Return the maximum monkey business, AKA the product of the 13 | // number of items handled by the two most rambunctious monkeys. 14 | monkey_game.max_monkey_business().into() 15 | } 16 | 17 | /// Represents the cruel and insensitive game being played by the monkeys, 18 | /// at your expense. Includes fields for the list of monkeys where their index 19 | /// also indicates their ID and for the items currently flying through the 20 | /// air from one monkey to another. 21 | pub struct CruelGame { 22 | items_in_flight: Vec<(u64, usize)>, 23 | monkeys: Vec, 24 | } 25 | 26 | impl CruelGame { 27 | /// Produce a `CruelGame` from a list of monkeys 28 | fn from(monkeys: &[Monkey]) -> Self { 29 | let monkeys = monkeys.to_vec(); 30 | let items_in_flight = Vec::new(); 31 | CruelGame { 32 | items_in_flight, 33 | monkeys, 34 | } 35 | } 36 | 37 | /// "Play" one round of the "game". 38 | fn play(&mut self) { 39 | // For each monkey... 40 | for id in 0..self.monkeys.len() { 41 | // Have the monkey handle each item it's currently holding, adding 42 | // items to the list of items in flight after handling each. 43 | self.monkeys[id].handle_items(&mut self.items_in_flight); 44 | 45 | // For each item in flight, have the monkey it was tossed to deftly 46 | // snatch it, along with your hopes and dreams, from the air. 47 | while let Some((item, target)) = self.items_in_flight.pop() { 48 | self.monkeys[target].catch(item); 49 | } 50 | } 51 | } 52 | 53 | /// Calculate and return the maximum monkey business. Identifies the number 54 | /// of items handled by each monkey and returns the product of the two 55 | /// largest totals. 56 | fn max_monkey_business(&self) -> u64 { 57 | let monkey_business: Vec<_> = self 58 | .monkeys 59 | .iter() 60 | .map(|m| m.inspected) 61 | .sorted_unstable() 62 | .rev() 63 | .take(2) 64 | .collect(); 65 | (monkey_business[0] * monkey_business[1]).into() 66 | } 67 | } 68 | 69 | impl Monkey { 70 | /// Have a monkey handle your precious items with callous disregard 71 | /// for your concerns. 72 | fn handle_items(&mut self, items_in_flight: &mut Vec<(u64, usize)>) { 73 | // For each item the monkey has... 74 | while let Some(mut item) = self.items.pop() { 75 | // Increase your worry over that item according to the puzzle rules. 76 | item = self.operation.apply(item); 77 | 78 | // Calm down a bit since the monkey didn't break it (this time). 79 | item /= 3; 80 | 81 | // Have the monkey decide on a target with a mischievous gleam in 82 | // its beady monkey eyes. 83 | let target = self.rule.check(item); 84 | 85 | // Toss the item to its intended target. 86 | items_in_flight.push((item, target)); 87 | 88 | // Increment the number of items this monkey has inspected 89 | self.inspected += 1; 90 | } 91 | } 92 | 93 | /// Catch an item thrown from another monkey. Probably pretend to fumble it 94 | /// or something just to get that human even more riled up. 95 | pub fn catch(&mut self, item: u64) { 96 | self.items.push(item); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/day11/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day11::{Input, Monkey, Operation, Output}; 2 | use itertools::Itertools; 3 | 4 | /// Solve Day 11, Part 2 5 | pub fn solve(input: &Input) -> Output { 6 | // Similar to last time, but somehow worse... 7 | let mut monkey_game = WorseGame::from(input); 8 | 9 | // One of the ways it's worse is that it goes on for 500x longer 10 | (0..10_000).for_each(|_| monkey_game.play_rough()); 11 | 12 | // Calculate and return the extreme level of monkey business 13 | monkey_game.max_monkey_business().into() 14 | } 15 | 16 | /// Represents the more intense version of the cruel, cruel game being played by 17 | /// these dastardly monkeys. Includes the same fields as the original `CruelGame`, 18 | /// plus the limit where your stress levels become overwhelming and you black out, 19 | /// experiencing short-term memory loss. 20 | pub struct WorseGame { 21 | items_in_flight: Vec<(u64, usize)>, 22 | monkeys: Vec, 23 | absolute_limit: u64, 24 | } 25 | 26 | impl WorseGame { 27 | // Start up a much worse version of the monkey game 28 | fn from(monkeys: &[Monkey]) -> Self { 29 | let monkeys = monkeys.to_vec(); 30 | let items_in_flight = Vec::new(); 31 | 32 | // In order to keep the computer from blowing its stack, too, we need to 33 | // identify the periodicity of the item worry values. This turns out to 34 | // be the least common multiple of all the monkey rule divisors by which 35 | // the monkeys check where to fling your things. Since all these divisors 36 | // are prime, this is the product of all the divisors. 37 | let absolute_limit = monkeys.iter().map(|m| m.rule.divisor).product(); 38 | 39 | WorseGame { 40 | items_in_flight, 41 | monkeys, 42 | absolute_limit, 43 | } 44 | } 45 | 46 | // The monkeys have upped their level of maliciousness and now pretend to drop 47 | // and break your items on each turn. You're pretty sure they've broken a few 48 | // things already, but they're throwing them so fast you can't tell for sure. 49 | fn play_rough(&mut self) { 50 | // For each monkey... 51 | for id in 0..self.monkeys.len() { 52 | // Have the monkey handle each item it's currently holding with obnoxious 53 | // roughness and glee, adding items to the list of items in flight after 54 | // handling each. 55 | self.monkeys[id].handle_items_roughly(self.absolute_limit, &mut self.items_in_flight); 56 | 57 | // For each item in flight, have the monkey it was tossed to deftly 58 | // snatch it, along with your hopes and dreams, from the air. 59 | while let Some((item, target)) = self.items_in_flight.pop() { 60 | self.monkeys[target].catch(item); 61 | } 62 | } 63 | } 64 | 65 | /// Calculate and return the maximum monkey business. Identifies the number 66 | /// of items handled by each monkey and returns the product of the two 67 | /// largest totals. 68 | fn max_monkey_business(&self) -> u64 { 69 | let monkey_business: Vec<_> = self 70 | .monkeys 71 | .iter() 72 | .map(|m| m.inspected) 73 | .sorted_unstable() 74 | .rev() 75 | .take(2) 76 | .collect(); 77 | (monkey_business[0] as u64 * monkey_business[1] as u64) 78 | } 79 | } 80 | 81 | impl Monkey { 82 | /// Have a monkey handle your precious items with exuberant malice, purposely 83 | /// stoking your alarm. 84 | fn handle_items_roughly( 85 | &mut self, 86 | absolute_limit: u64, 87 | items_in_flight: &mut Vec<(u64, usize)>, 88 | ) { 89 | while let Some(mut item) = self.items.pop() { 90 | // Increase your worry over that item according to the puzzle rules. 91 | item = self.operation.apply(item); 92 | 93 | // Black out for a moment from the stress caused by these monkeys 94 | // tossing your precious things about, experiencing an odd form of 95 | // amnesia and "resetting" your stress levels a bit. 96 | item %= absolute_limit; 97 | 98 | // Have the monkey decide on a target with a malicious glint in 99 | // its beady monkey eyes. 100 | let target = self.rule.check(item); 101 | 102 | // Toss the item to its intended target. 103 | items_in_flight.push((item, target)); 104 | 105 | // Increment the number of items this monkey has inspected 106 | self.inspected += 1; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/day11/shared.rs: -------------------------------------------------------------------------------- 1 | use super::{Monkey, Operation, Rule}; 2 | use itertools::Itertools; 3 | 4 | impl Operation { 5 | /// Apply an operation to an item's worry score. 6 | pub fn apply(&self, item: u64) -> u64 { 7 | match self { 8 | Operation::Add(n) => item + n, 9 | Operation::Mult(n) => item * n, 10 | Operation::Square => item * item, 11 | } 12 | } 13 | } 14 | 15 | impl Rule { 16 | /// Check an item's worry score and return which monkey ID to throw 17 | /// the item to. 18 | pub fn check(&self, item: u64) -> usize { 19 | if item % self.divisor == 0 { 20 | self.success 21 | } else { 22 | self.fail 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/day12/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{Hill, HillMap}; 7 | 8 | pub type Input = HillMap; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 472); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 465); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day12/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day12::{Hill, HillMap, Input, Output}; 2 | use core::cmp::Reverse; 3 | use std::collections::{BinaryHeap, HashMap}; 4 | 5 | /// Solve Day 12, Part 1 6 | pub fn solve(input: &Input) -> Output { 7 | // Starting at the start hill, count the number of steps to the end hill 8 | // with an implementation of Dijkstra's algorithm. 9 | let start_at = input.start_at; 10 | input.shortest_path_to_summit(start_at).unwrap().into() 11 | } 12 | 13 | impl HillMap { 14 | /// Dijkstra's Algorithm!!! 15 | pub fn shortest_path_to_summit(&self, start_at: (usize, usize)) -> Option { 16 | // The 'open set': the hills we know how to travel to, but don't 17 | // know how to travel _from_ yet. You can think of this like the expanding 18 | // outer edge of our search space, if that helps. Because it's a binary 19 | // heap (we're using as a _min_ binary heap), the next hill to be fetched 20 | // will always be the one with the shortest travel time found so far. 21 | let mut open = BinaryHeap::from([(Reverse(0), start_at)]); 22 | 23 | // Maintain a listing of the shortest number of steps to each hill we've 24 | // traveled to. It's the shortest number of steps _so far_, it's possible 25 | // to update these if a shorter path is found. 26 | let mut steps = HashMap::from([(start_at, 0)]); 27 | 28 | // So long as there are hills left to climb... 29 | while let Some((_, pos)) = open.pop() { 30 | // Check the hill we're currently on. If it's the end, then just return 31 | // the number of steps it took us to get here. 32 | let (row, col) = pos; 33 | if pos == self.end_at { 34 | return steps.get(&pos).copied(); 35 | } 36 | 37 | // Otherwise, see if this hill has any neighbors we can reach. If not, 38 | // skip it and move on to the next hill in our 'open set'. 39 | let Some(neighbors) = self.graph.get(&pos) else { continue; }; 40 | 41 | // For each direction where there might be a neighbor... 42 | for maybe_neighbor in neighbors { 43 | // If there's no reachable neighbor, try the next direction. 44 | let Some(neighbor) = maybe_neighbor else { continue; }; 45 | 46 | // Otherwise, calculate how many steps it will take to get to that 47 | // neighbor from the path you're currently on. That is, one more step 48 | // than it took to get to the current hill. 49 | let next_steps: u32 = steps.get(&pos).unwrap() + 1; 50 | 51 | // Check how many steps are in the current shortest path to that neighbor 52 | let curr_steps: u32 = *steps.get(neighbor).unwrap_or(&u32::MAX); 53 | 54 | // If we've already found a shorter way to get there, we can just 55 | // move on. 56 | if next_steps >= curr_steps { 57 | continue; 58 | } 59 | 60 | // If we're on the shortest path, then add the neighbor to the open 61 | // set and record the number of steps 62 | open.push((Reverse(next_steps), *neighbor)); 63 | steps.insert(*neighbor, next_steps); 64 | } 65 | } 66 | 67 | None 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/day12/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day12::{Hill, HillMap, Input, Output}; 2 | use core::cmp::Reverse; 3 | use std::cmp::min; 4 | use std::collections::{BinaryHeap, HashMap}; 5 | 6 | /// Solve Day 12, Part 2 7 | pub fn solve(input: &Input) -> Output { 8 | // Turns out we need to 'invert' our `HillMap` in order to efficiently find the 9 | // shortest path to _any_ hill with a height of 0. 10 | let descent_map = DescentMap::from(input); 11 | 12 | // Get the number of steps from the summit to _every_ single hill in 13 | // the jungle! 14 | let steps_to_short_hills = descent_map.shortest_paths_from_summit(); 15 | 16 | // Check each hill to see if it's a short hill, and if it is, check to see if 17 | // it's the shortest path to a short hill found so far. If so, record it! 18 | let mut shortest_path = u32::MAX; 19 | for (pos, steps_to_pos) in steps_to_short_hills.iter() { 20 | let (row, col) = *pos; 21 | let Hill::Hill(0) = descent_map.hills[row][col] else { continue; }; 22 | shortest_path = min(shortest_path, *steps_to_pos); 23 | } 24 | 25 | // Return the shortest path to a short hill 26 | shortest_path.into() 27 | } 28 | 29 | // Type alias we'll use here to refer to the hills that can reach the current hill 30 | type Neighbors = [Option<(usize, usize)>; 4]; 31 | 32 | // Very much like the `HillMap`. The biggest difference is that now `graph` represents 33 | // relationships between hills that can be moved _to_ and the hills that can reach 34 | // them (the reverse of the relationship for `HillMap`). 35 | struct DescentMap { 36 | hills: Vec>, 37 | graph: HashMap<(usize, usize), Neighbors>, 38 | summit: (usize, usize), 39 | } 40 | 41 | // Produce a `DescentMap` from a reference to a `HillMap`. 42 | impl From<&HillMap> for DescentMap { 43 | fn from(hill_map: &HillMap) -> Self { 44 | // We need to invert the graph, so that we can essentially walk backwards 45 | // starting from the summit (our previous end point) down to all those 46 | // short hills. 47 | let mut graph: HashMap<(usize, usize), Neighbors> = HashMap::new(); 48 | 49 | // For each entry in the `HillMap`s graph... 50 | for (pos, neighbors) in hill_map.graph.iter() { 51 | // For each neighbor in the entry's list of neighbors (skipping the empty 52 | // spaces in the neighbor array) 53 | for neighbor in neighbors.iter().flatten() { 54 | // Yeah, this is a bit of a gnarly iterator chain. Here's what's going 55 | // on: We're checking the entry in our inverted `graph` where the 56 | // neighbor is the key, creating an entry with an empty set of 57 | // neighbors if the neighbor doesn't have an entry yet. Then, for each 58 | // slot in the value array for `neighbor`, find the first index that 59 | // doesn't have a value yet and put `pos` there. This 'inverts' the 60 | // relationships by making `neighbor` the key and adding `pos` as one 61 | // of the positions from which `neighbor` can be reached. 62 | graph 63 | .entry(*neighbor) 64 | .or_default() 65 | .iter_mut() 66 | .filter(|slot| slot.is_none()) 67 | .take(1) 68 | .for_each(|slot| *slot = Some(*pos)); 69 | } 70 | } 71 | 72 | // Copy the `hills` and `end_at` fields from the `HillMap` 73 | let hills = hill_map.hills.to_vec(); 74 | let summit = hill_map.end_at; 75 | 76 | // Return the new `DescentMap` with the inverted graph. 77 | DescentMap { 78 | hills, 79 | graph, 80 | summit, 81 | } 82 | } 83 | } 84 | 85 | impl DescentMap { 86 | /// Identify and return the minimum number of steps every other hill is from 87 | /// the summit as a HashMap where the keys are hill positions and the values 88 | /// are the number of steps from the summit. 89 | pub fn shortest_paths_from_summit(&self) -> HashMap<(usize, usize), u32> { 90 | // The procedure here is the same Dijkstra's algorithm from part one, just 91 | // walking down from the summit instead of up from the start space. 92 | let start_at = self.summit; 93 | let mut open = BinaryHeap::from([(Reverse(0), start_at)]); 94 | let mut steps = HashMap::from([(start_at, 0)]); 95 | 96 | // While there are still hills to explore... 97 | while let Some((_, pos)) = open.pop() { 98 | // No need for an early return here, we want to find a path to _all_ the 99 | // other hills. 100 | 101 | // As before, we check all the neighbors and any time we're able to 102 | // reach that neighbor by the shortest path found so far, we add that 103 | // neighbor to the open set. 104 | let Some(neighbors) = self.graph.get(&pos) else { continue; }; 105 | for maybe_neighbor in neighbors { 106 | let Some(neighbor) = maybe_neighbor else { continue; }; 107 | let next_steps: u32 = steps.get(&pos).unwrap() + 1; 108 | let curr_steps: u32 = *steps.get(neighbor).unwrap_or(&u32::MAX); 109 | if next_steps >= curr_steps { 110 | continue; 111 | } 112 | open.push((Reverse(next_steps), *neighbor)); 113 | steps.insert(*neighbor, next_steps); 114 | } 115 | } 116 | 117 | // Returns a mapping of the fewest steps to every hill from the summit 118 | steps 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/day13/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day13::Input; 2 | 3 | /// Represnts a Packet. Packet data consists of lists and integer (that's what the 4 | /// puzzle says, anyway). 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub enum Packet { 7 | Integer(u8), 8 | List(Vec), 9 | } 10 | 11 | /// Represents a pair of packets. Riveting stuff! 12 | #[derive(Debug, Clone)] 13 | pub struct PacketPair(pub Packet, pub Packet); 14 | 15 | /// Here's where the magic happens. This module wraps the parsers for the list of 16 | /// packet pairs presented in the input. 17 | pub mod parser { 18 | use super::*; 19 | use anyhow::{anyhow, Result}; 20 | use nom::{ 21 | branch::alt, 22 | bytes::complete::tag, 23 | character::complete::{newline, u8}, 24 | combinator::map, 25 | multi::{separated_list0, separated_list1}, 26 | sequence::{delimited, separated_pair}, 27 | Finish, IResult, 28 | }; 29 | 30 | /// Nom parser for "2" -> Packet::Integer(2) 31 | fn integer(s: &str) -> IResult<&str, Packet> { 32 | map(u8, Packet::Integer)(s) 33 | } 34 | 35 | /// This is where the recursion happens. This parser parses lists of 36 | /// packet information into their appropriate packet representation. 37 | /// Parses all of the following: 38 | /// - "[]" -> Packet::List([]) 39 | /// - "[5]" -> Packet::List([Packet::Integer(5)]) 40 | /// - "[1, 2]" -> Packet::List([Packet::Integer(1), Packet::Integer(2)]) 41 | /// - "[1, [2]]" -> Packet::List([Packet::Integer(1), Packet::List([Packet::Integer(2)])]) 42 | fn list(s: &str) -> IResult<&str, Packet> { 43 | let list_contents = separated_list0(tag(","), packet); 44 | map(delimited(tag("["), list_contents, tag("]")), Packet::List)(s) 45 | } 46 | 47 | /// Packet data consists of lists and integers. This parser will parse a `Packet` 48 | /// from a list or things or from a single integer. 49 | fn packet(s: &str) -> IResult<&str, Packet> { 50 | alt((integer, list))(s) 51 | } 52 | 53 | /// Parses two packets separated by a newline into a `PacketPair` 54 | fn packet_pair(s: &str) -> IResult<&str, PacketPair> { 55 | let (s, (first, second)) = separated_pair(packet, newline, packet)(s)?; 56 | Ok((s, PacketPair(first, second))) 57 | } 58 | 59 | /// Parses a list of packet pairs separated by an empty line into a `Vec` 60 | pub fn parse(s: &str) -> Result> { 61 | let result = separated_list1(tag("\n\n"), packet_pair)(s).finish(); 62 | let (_, pair_list) = result.map_err(|e| anyhow!("{e}"))?; 63 | Ok(pair_list) 64 | } 65 | } 66 | 67 | const INPUT: &str = include_str!("../../input/13/input.txt"); 68 | 69 | /// Parse that input! 70 | pub fn read() -> Input { 71 | parser::parse(INPUT).unwrap() 72 | } 73 | 74 | #[cfg(test)] 75 | mod test { 76 | use super::*; 77 | use std::fmt::{Display, Formatter, Result as FmtResult}; 78 | 79 | impl Display for Packet { 80 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 81 | match self { 82 | Packet::Integer(i) => write!(f, "{i}"), 83 | Packet::List(l) => { 84 | write!(f, "[")?; 85 | for (idx, p) in l.iter().enumerate() { 86 | write!(f, "{p}")?; 87 | if idx < l.len() - 1 { 88 | write!(f, ",")?; 89 | } 90 | } 91 | write!(f, "]") 92 | } 93 | } 94 | } 95 | } 96 | 97 | impl Display for PacketPair { 98 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 99 | let PacketPair(first, second) = self; 100 | writeln!(f, "{first}")?; 101 | writeln!(f, "{second}") 102 | } 103 | } 104 | 105 | #[test] 106 | fn check_input() { 107 | // To test, I'm just reading and parsing the input, then converting it back 108 | // into a string and comparing it to the original. 109 | let input = parser::parse(INPUT).unwrap(); 110 | let from_parsed: String = input 111 | .iter() 112 | .map(|p| p.to_string()) 113 | .intersperse(String::from("\n")) 114 | .collect(); 115 | assert_eq!(INPUT, from_parsed); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/day13/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | pub mod shared; 5 | 6 | use crate::{Output, Part}; 7 | use input::{parser, Packet, PacketPair}; 8 | 9 | pub type Input = Vec; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 6072); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 22184); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day13/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day13::{Input, Output, PacketPair}; 2 | 3 | /// Solve Day 13, Part 1 4 | pub fn solve(input: &Input) -> Output { 5 | let mut total = 0; // The total index value of proper sorted pairs 6 | 7 | // For each pair of packets... 8 | for (idx, packet_pair) in input.iter().enumerate() { 9 | // If it's not sorted correctly, skip. 10 | if !packet_pair.is_sorted() { 11 | continue; 12 | } 13 | 14 | // Otherwise, add the value of its index to the total 15 | total += (idx as u32) + 1; // Packets are 1-indexed 16 | } 17 | total.into() 18 | } 19 | 20 | impl PacketPair { 21 | /// Indicates if the first packet in a pair is less than the second 22 | /// packet in the pair. 23 | fn is_sorted(&self) -> bool { 24 | let Self(first, second) = self; 25 | first < second 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/day13/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day13::{parser, Input, Output, Packet, PacketPair}; 2 | 3 | /// Solve Day 13, Part 2 4 | pub fn solve(input: &Input) -> Output { 5 | use Packet::*; // For syntax 6 | 7 | // Define the two divider packets and put them in an array to add them 8 | // to the list of all the packets. 9 | let divider1 = List(vec![List(vec![Integer(2)])]); 10 | let divider2 = List(vec![List(vec![Integer(6)])]); 11 | let dividers = [divider1, divider2]; 12 | 13 | // Flatten all the pairs of packets into a list of packets with the two 14 | // divider packets added to the end. 15 | let mut all_packets = input 16 | .iter() 17 | .cloned() 18 | .flatten() 19 | .chain(dividers.iter().cloned()) 20 | .collect::>(); 21 | 22 | // Sort all the packets! No need to do any more work than that, since 23 | // by implementing Ord and PartialOrd, we've done all we need to 24 | // sort them. 25 | all_packets.sort_unstable(); 26 | 27 | // Find the indices of the two divider packets and return their product. 28 | let mut total = 1; 29 | for (idx, packet) in all_packets.iter().enumerate() { 30 | if dividers.contains(packet) { 31 | total *= (idx as u32) + 1; 32 | } 33 | } 34 | total.into() 35 | } 36 | 37 | // It's easier to flatten the packet pairs into a 1D list when we can 38 | // iterate over the two packets contained in them. 39 | impl IntoIterator for PacketPair { 40 | type Item = Packet; 41 | type IntoIter = std::array::IntoIter; 42 | 43 | fn into_iter(self) -> Self::IntoIter { 44 | let PacketPair(first, second) = self; 45 | [first, second].into_iter() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/day13/shared.rs: -------------------------------------------------------------------------------- 1 | use super::Packet; 2 | use std::cmp::Ordering; 3 | 4 | /// Partial ordering for `Packet`s. Defining this would be enough to use 5 | /// `Packet` < `Packet`, but we won't be able to do a full sort without 6 | /// implementing `Ord` as well. 7 | impl PartialOrd for Packet { 8 | fn partial_cmp(&self, other: &Self) -> Option { 9 | Some(self.cmp(other)) 10 | } 11 | } 12 | 13 | /// Defines the result of comparing one `Packet` to another, used for ordinal 14 | /// comparisons and sorting. Returns an enum that indicates whether `self` is 15 | /// less than, greater than, or even equal to `other`. 16 | impl Ord for Packet { 17 | fn cmp(&self, other: &Self) -> Ordering { 18 | use Packet::*; // For syntax 19 | match (self, other) { 20 | // When comparing two packet integers, just compare them by value 21 | (Integer(i1), Integer(i2)) => i1.cmp(i2), 22 | 23 | // When comparing a packet integer to a packet list, convert the integer 24 | // to a single item list and compare the two lists. 25 | (Integer(i), List(_)) => List(vec![Integer(*i)]).cmp(other), 26 | (List(_), Integer(i)) => self.cmp(&List(vec![Integer(*i)])), 27 | 28 | // When comparing two lists, compare item by item and return the first 29 | // result where the two items aren't equal. If one list has more items 30 | // than another and all the values up to the length of the shortest list 31 | // are equal, then we compare the length of the lists. Turns out, this 32 | // is exactly how comparing two vectors works _by default_! Woot! 33 | (List(l1), List(l2)) => l1.cmp(l2), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/day14/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{Offset, Point}; 7 | use part1::CaveMap; 8 | use std::collections::HashSet; 9 | 10 | pub type Input = HashSet; 11 | 12 | pub fn run(part: Part) -> Output { 13 | let input = input::read(); 14 | match part { 15 | Part::One => part1::solve(&input), 16 | Part::Two => part2::solve(&input), 17 | } 18 | } 19 | 20 | pub fn run_both() -> (Output, Output) { 21 | let input = input::read(); 22 | (part1::solve(&input), part2::solve(&input)) 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn check_answer_one() { 31 | let result = run(Part::One); 32 | assert_eq!(result, 625); 33 | } 34 | 35 | #[test] 36 | fn check_answer_two() { 37 | let result = run(Part::Two); 38 | assert_eq!(result, 25193); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/day14/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day14::{Input, Offset, Output, Point}; 2 | use std::collections::HashSet; 3 | 4 | /// Solve Day 14, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | // Turn that set of impassable points into a `CaveMap` 7 | let mut cave_map = CaveMap::new(input.clone()); 8 | 9 | // We'll drop up to 10K grains of sand. This is overkill, but I'd rather 10 | // use a `for` loop here instead of a `loop`. Mostly so I have the grain 11 | // number in the loop without maintaining a separate variable and mutating 12 | // it each loop. 13 | for grains in 1..10_000 { 14 | // When we find the first grain of sand that falls into the infinite 15 | // abyss, we stop and return the current grain count minus one as 16 | // the number of grains _before_ this poor soul was lost to the void. 17 | if let GrainStatus::LostToTheAbyss = cave_map.add_sand() { 18 | return (grains - 1).into(); 19 | } 20 | } 21 | 22 | // If we ever get here, something has gone horribly wrong 23 | unreachable!() 24 | } 25 | 26 | /// This enum represents the status of a grain of sand flowing down from the 27 | /// ceiling. May represent the result of a single step or the entire flow of 28 | /// that grain from start to finish. 29 | #[derive(Debug)] 30 | enum GrainStatus { 31 | MovedTo(Point), 32 | StoppedAt(Point), 33 | LostToTheAbyss, 34 | } 35 | 36 | /// Represents the map of our cave. We're keeping up with a set of the points in 37 | /// space that sand can't flow through in `obstacles`, the point where the sand 38 | /// enters the map in `entrypoint`, and the y-index of the lowest point containing 39 | /// rock in `depth`. Past that is the timeless void. 40 | #[derive(Debug, Clone)] 41 | pub struct CaveMap { 42 | pub obstacles: HashSet, 43 | pub entrypoint: Point, 44 | pub depth: u32, 45 | } 46 | 47 | impl CaveMap { 48 | /// Create a new `CaveMap` from a set of points representing impassable points 49 | /// in the cave (for sand). 50 | pub fn new(obstacles: HashSet) -> Self { 51 | // Find the lowest y-coordinate for any rock 52 | let depth = obstacles 53 | .iter() 54 | .map(|point| point.1) 55 | .max() 56 | .unwrap_or_default(); 57 | 58 | // Could have been a constant... 59 | let entrypoint = Point(500, 0); 60 | 61 | CaveMap { 62 | obstacles, 63 | entrypoint, 64 | depth, 65 | } 66 | } 67 | 68 | /// Add one grain of sand to the map and follow it as it flows down. Returns 69 | /// the final status of the grain. 70 | fn add_sand(&mut self) -> GrainStatus { 71 | let mut sand = self.entrypoint; // Sand flows in from here 72 | 73 | // Infinite loop!!! It'll stop eventually (we hope). 74 | loop { 75 | // Try to flow the grain of sand down one step 76 | let sand_flow = self.try_move_sand(sand); 77 | 78 | // Do different things depending on what happened when the grain of sand 79 | // attempted to move down one step. 80 | match sand_flow { 81 | // If the grain moved to a new point, update the grain and keep going 82 | GrainStatus::MovedTo(point) => sand = point, 83 | 84 | // If the grain stopped moving, add it as an obstacle to the cave 85 | // and break the loop, returning this final status of the grain. 86 | GrainStatus::StoppedAt(point) => { 87 | self.obstacles.insert(point); 88 | break sand_flow; 89 | } 90 | 91 | // If the grain of sand is now tumbling through the dark, slowly 92 | // forgetting what the sun looks like, break the loop and return 93 | // this depressing (for the grain of sand) result. 94 | GrainStatus::LostToTheAbyss => break sand_flow, 95 | } 96 | } 97 | } 98 | 99 | /// Try to move a grain of sand from it's current position downwards. 100 | fn try_move_sand(&self, sand: Point) -> GrainStatus { 101 | // A grain of sand can try to move down, down-left, and down-right, in 102 | // that order. 103 | let offsets = [Offset(0, 1), Offset(-1, 1), Offset(1, 1)]; 104 | 105 | // Try each step in order... 106 | for offset in offsets { 107 | // The position that we want the grain of sand to take 108 | let try_pos = sand + offset; 109 | 110 | // If there's an obstacle there, then try moving another direction 111 | if self.obstacles.contains(&try_pos) { 112 | continue; 113 | } 114 | 115 | // If we're at the y-coordinate representing the depth, then we know 116 | // there is no hope for the grain of sand and it will spin down into 117 | // darkness. Return this status. 118 | if sand.1 >= self.depth { 119 | return GrainStatus::LostToTheAbyss; 120 | } 121 | 122 | // If the grain were able to move to the new position, return that result. 123 | return GrainStatus::MovedTo(try_pos); 124 | } 125 | 126 | // If all three directions were tried and failed, this grain can go no 127 | // further. Return that status. 128 | GrainStatus::StoppedAt(sand) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/day14/part2.rs: -------------------------------------------------------------------------------- 1 | use crate::day14::{CaveMap, Input, Offset, Output, Point}; 2 | use std::collections::{HashSet, VecDeque}; 3 | 4 | /// Solve Day 14, Part 2 5 | pub fn solve(input: &Input) -> Output { 6 | // Turn that set of impassable points into a `CaveMap` 7 | let cave_map = CaveMap::new(input.clone()); 8 | 9 | // Now turn that `CaveMap` into a `FillMap` to determine how 10 | // much sand it takes to fill the pile. 11 | let mut fill_map = FillMap::from(cave_map); 12 | 13 | // Pour sand into the cave until it fills up to the entrypoint 14 | // and report the number of grains it took to do so. 15 | fill_map.sand_capacity().into() 16 | } 17 | 18 | /// A slight variation on the `CaveMap`. Mostly using a new struct for different 19 | /// behaviors more than different data types. 20 | #[derive(Debug, Clone)] 21 | pub struct FillMap { 22 | obstacles: HashSet, 23 | entrypoint: Point, 24 | depth: u32, 25 | } 26 | 27 | impl FillMap { 28 | /// Unpack a `CaveMap` into a `FillMap` 29 | fn from(grid_map: CaveMap) -> Self { 30 | // Get the attributes from the `CaveMap` 31 | let CaveMap { 32 | obstacles, 33 | entrypoint, 34 | depth, 35 | } = grid_map; 36 | 37 | // Adjust the depth to represent the floor. Hey, look, there's that grain of 38 | // sand we thought was gone forever, breathing a huge sigh of relief. Good 39 | // for him! 40 | let depth = depth + 2; 41 | 42 | // Now it's a `FillMap`! 43 | FillMap { 44 | obstacles, 45 | entrypoint, 46 | depth, 47 | } 48 | } 49 | 50 | /// From a given Point, return an array indicating which points a grain of sand 51 | /// can flow into (e.g., that aren't blocked by an obstacle or the floor). 52 | fn get_neighbors(&self, point: Point) -> [Option; 3] { 53 | // The same three potential moves as the first part 54 | let offsets = [Offset(0, 1), Offset(-1, 1), Offset(1, 1)]; 55 | 56 | // Array to hold the neighbors that can be moved to 57 | let mut neighbors = [None; 3]; 58 | 59 | // For each possible offset... 60 | for (idx, offset) in offsets.iter().enumerate() { 61 | // The position we might move to. 62 | let try_pos = point + *offset; 63 | 64 | // If there's an obstacle there, skip it. Can't move there. 65 | if self.obstacles.contains(&try_pos) { 66 | continue; 67 | } 68 | 69 | // If there's floor there, skip it. Can't move there. 70 | if try_pos.1 >= self.depth { 71 | continue; 72 | } 73 | 74 | // Otherwise, we can move there. Add this point to our neighbors array. 75 | neighbors[idx] = Some(try_pos); 76 | } 77 | 78 | // Return the list of neighbors 79 | neighbors 80 | } 81 | 82 | /// Calculate the number of sand grains it'll take to fill in the pile and 83 | /// block off the entrypoint. Using Dijkstra's Algorithm! Nah, just kidding, 84 | /// it's a breadth-first search. 85 | fn sand_capacity(&self) -> u32 { 86 | let mut queue = VecDeque::from([self.entrypoint]); 87 | let mut visited = HashSet::new(); 88 | let mut counted = 0; // Keep up with the number of grains 89 | 90 | // So long as we've got positions to try moving _from_... 91 | while let Some(point) = queue.pop_back() { 92 | // If we've visited this space before, skip it. Been here, done that. 93 | if visited.contains(&point) { 94 | continue; 95 | } 96 | visited.insert(point); // Mark `point` as visited 97 | counted += 1; // Count this grain of sand 98 | 99 | // For each reachable neighbor point from the current point 100 | for neighbor in self.get_neighbors(point).iter().flatten() { 101 | // If we've visited that point before, skip it. 102 | if visited.contains(neighbor) { 103 | continue; 104 | } 105 | 106 | // Add that point to the list of points to visit 107 | queue.push_front(*neighbor); 108 | } 109 | } 110 | 111 | counted // Return the number of grains of sand we counted 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/day15/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day15::Input; 2 | 3 | /// Represents a point in the 2D plane where our sensors are located 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 5 | pub struct Point(pub isize, pub isize); 6 | 7 | impl Point { 8 | // Calculate the Manhattan Distance from one Point to another Point 9 | pub fn distance_to(&self, other: &Self) -> usize { 10 | let Point(x1, y1) = self; 11 | let Point(x2, y2) = other; 12 | x1.abs_diff(*x2) + y1.abs_diff(*y2) 13 | } 14 | } 15 | 16 | /// Easily create a Point from a tuple of i32's. This is mostly here because 17 | /// the `nom` parsers don't provide isize directly. 18 | impl From<(i32, i32)> for Point { 19 | fn from(value: (i32, i32)) -> Self { 20 | let (x, y) = value; 21 | Point(x as isize, y as isize) 22 | } 23 | } 24 | 25 | /// Represents one of our sensors. Encapsulates the location of the beacon it is 26 | /// detecting and the Senso's detection range. 27 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 28 | pub struct Sensor { 29 | pub location: Point, 30 | pub beacon: Point, 31 | pub range: usize, 32 | } 33 | 34 | impl Sensor { 35 | fn new(location: Point, beacon: Point) -> Self { 36 | let range = location.distance_to(&beacon); 37 | Sensor { 38 | location, 39 | beacon, 40 | range, 41 | } 42 | } 43 | } 44 | 45 | /// Convert a tuple of Points into a Sensor. The first Point is the location of the 46 | /// Sensor, the second is the location of the beacon. 47 | impl From<(Point, Point)> for Sensor { 48 | fn from(value: (Point, Point)) -> Self { 49 | let (location, beacon) = value; 50 | Sensor::new(location, beacon) 51 | } 52 | } 53 | 54 | /// Internal module wrapping the `nom` parser for the input 55 | mod parser { 56 | use super::*; 57 | use anyhow::{anyhow, Result}; 58 | use nom::{ 59 | bytes::complete::take_till, 60 | character::{ 61 | complete::{i32, newline}, 62 | is_alphabetic, is_digit, 63 | }, 64 | combinator::{map, recognize}, 65 | multi::separated_list0, 66 | sequence::{pair, preceded}, 67 | Finish, IResult, 68 | }; 69 | 70 | /// Nom parser to skip everything that's not a number or minus sign 71 | fn till_number(s: &str) -> IResult<&str, &str> { 72 | take_till(|c: char| c.is_ascii_digit() || c == '-')(s) 73 | } 74 | 75 | /// Nom parser to parse an i32 with a bunch of cruft in front of it 76 | fn prefixed_number(s: &str) -> IResult<&str, i32> { 77 | preceded(till_number, i32)(s) 78 | } 79 | 80 | /// Nom parser to take the first two found numbers and return a Point from them 81 | fn point(s: &str) -> IResult<&str, Point> { 82 | map(pair(prefixed_number, prefixed_number), Point::from)(s) 83 | } 84 | 85 | /// Get the two Points from an input line 86 | fn sensor(s: &str) -> IResult<&str, Sensor> { 87 | map(pair(point, point), Sensor::from)(s) 88 | } 89 | 90 | /// Parse all lines from the input into a list of Sensors 91 | fn sensors(s: &str) -> IResult<&str, Vec> { 92 | separated_list0(newline, sensor)(s) 93 | } 94 | 95 | /// Parse the input, returns a list of Sensors 96 | pub fn parse(s: &str) -> Result> { 97 | let (_, result) = sensors(s).finish().map_err(|e| anyhow!("{e}"))?; 98 | Ok(result) 99 | } 100 | } 101 | 102 | const INPUT: &str = include_str!("../../input/15/input.txt"); 103 | 104 | /// Parse that input! 105 | pub fn read() -> Input { 106 | let mut input = parser::parse(INPUT).unwrap(); 107 | input.sort_unstable(); 108 | input 109 | } 110 | 111 | #[cfg(test)] 112 | mod test { 113 | use super::*; 114 | use itertools::Itertools; 115 | 116 | #[test] 117 | fn check_input() { 118 | let input = read(); 119 | assert_eq!(input.len(), 26); 120 | 121 | let first_found = *input.first().unwrap(); 122 | let first_expected = Sensor::new(Point(3772068, 2853720), Point(4068389, 2345925)); 123 | assert_eq!(first_found, first_expected); 124 | 125 | let last_found = *input.last().unwrap(); 126 | let last_expected = Sensor::new(Point(2712265, 2155055), Point(2700909, 2519581)); 127 | assert_eq!(last_found, last_expected); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/day15/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{Point, Sensor}; 7 | 8 | pub type Input = Vec; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 5299855); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 13615843289729u64); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day15/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day15::{Input, Output, Point, Sensor}; 2 | use itertools::Itertools; 3 | 4 | /// Solve Day 15, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | let row = 2_000_000; // Our hard-coded row of interest 7 | 8 | // Identify a RowRange for each sensor indicated the furthest left and furthest 9 | // right point detected by each sensor. These RowRanges may overlap, though, so 10 | // we need to 'condense' the ranges so we don't double-count any points. This 11 | // part depends on the input being sorted ahead of time, so that the RowRanges 12 | // come out sorted, so that all the RowRanges that can be condensed will be 13 | // encountered one after another. If the input isn't sorted here, you'll get a 14 | // different (wrong) answer. 15 | let mut ranges: Vec = Vec::new(); 16 | for range in input.iter().flat_map(|s| s.row_range_sensed(row)) { 17 | // If the current range can be merged with the last range in `ranges`, do so. 18 | // Otherwise, just push the current range to the list. 19 | if let Some(last_rng) = ranges.last_mut() { 20 | if last_rng.overlaps(&range) { 21 | last_rng.merge(&range); 22 | } 23 | continue; 24 | } 25 | ranges.push(range); 26 | } 27 | 28 | // Count the number of observable positions on the target row, defined by 29 | // the limits of the `ranges`. 30 | let sensed_on_row = ranges.iter().map(|r| r.count_positions()).sum::(); 31 | 32 | // We'll need to subtract out the number of beacons on the row, since those 33 | // points definitely _can_ contain a beacon. 34 | let beacons_on_row = input 35 | .iter() 36 | .filter_map(|s| s.beacon_on_row(row)) 37 | .unique() 38 | .count(); 39 | 40 | let definitely_not_beacons = sensed_on_row - beacons_on_row; 41 | (definitely_not_beacons as u32).into() 42 | } 43 | 44 | /// Represents a range of points on a given row of the scan. Includes the start and 45 | /// end points on that row. 46 | #[derive(Debug)] 47 | struct RowRange(isize, isize); 48 | 49 | impl RowRange { 50 | /// Does this range overlap another? 51 | fn overlaps(&self, other: &Self) -> bool { 52 | other.1 >= self.0 && self.1 >= other.0 53 | } 54 | 55 | /// Merge this range with another 56 | fn merge(&mut self, other: &Self) { 57 | *self = RowRange(self.0.min(other.0), self.1.max(other.1)); 58 | } 59 | 60 | /// Count the number of positions in this range 61 | fn count_positions(&self) -> usize { 62 | self.0.abs_diff(self.1) + 1 63 | } 64 | } 65 | 66 | impl Sensor { 67 | /// Indicates if the sensor can detect the given Point 68 | pub fn can_detect(&self, point: &Point) -> bool { 69 | self.location.distance_to(point) <= self.range 70 | } 71 | 72 | /// Workhorse of part one. Identifies and returns the range of positions 73 | /// that can be detected by this sensor on the indicated row, as a RowRange. 74 | fn row_range_sensed(&self, row: isize) -> Option { 75 | let distance_to_row = self.location.1.abs_diff(row); 76 | if distance_to_row > self.range { 77 | return None; 78 | } 79 | 80 | // The spread indicates how much of the Manhattan distance for detection 81 | // is remaining to 'spread' out to the left and right. Essentially half 82 | // the width of the detection zone on this row. 83 | let spread = self.range - distance_to_row; 84 | let range_start = self.location.0.saturating_sub_unsigned(spread); 85 | let range_end = self.location.0.saturating_add_unsigned(spread); 86 | Some(RowRange(range_start, range_end)) 87 | } 88 | 89 | /// If the beacon is on the given row, return the location of the beacon. 90 | /// Otherwise, return None. 91 | fn beacon_on_row(&self, row: isize) -> Option { 92 | if self.beacon.1 == row { 93 | return Some(self.beacon); 94 | } 95 | None 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/day16/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::ValveMap; 7 | 8 | pub type Input = ValveMap; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 1641); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 2261); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day16/part1.rs: -------------------------------------------------------------------------------- 1 | use crate::day16::{Input, Output, ValveMap}; 2 | 3 | /// Solve Day 16, Part 1 4 | /// 5 | /// I'm not satisfied with this solution. We're essentially performing a depth-first 6 | /// search starting from "AA" and identifying _every_ path through the notable valves 7 | /// then returning the maximum pressure released by _any_ path. I'm quite certain 8 | /// that there's an A* implementation that could do this much more efficiently, but 9 | /// I'm struggling to develop an appropriate penalty function. I'll come back to this 10 | /// one. 11 | pub fn solve(input: &Input) -> Output { 12 | // Start at valve "AA" 13 | let state = TravelState::default(); 14 | let mut open = vec![state]; 15 | 16 | // Depth-first seach starting with "AA". Every time we hit a state where 17 | // time has run out, we check the total pressure released and update 18 | // the maximum pressure if necessary. 19 | let mut max_released = 0; 20 | 21 | // While the search stack has items remaining... 22 | while let Some(state) = open.pop() { 23 | // Check the state on the top of the stack. If time is up, try to update 24 | // the max pressure released and move on to the next item in the stack. 25 | if state.remaining == 0 { 26 | max_released = max_released.max(state.released); 27 | continue; 28 | } 29 | 30 | // If the state is an intermediate state (time still remaining), then add 31 | // each possible next state to the stack. 32 | for next_state in state.next_states(input) { 33 | open.push(next_state); 34 | } 35 | } 36 | 37 | // Return the maximum pressure released by any path through the valves 38 | max_released.into() 39 | } 40 | 41 | /// Represents the state of a path through the valves. Indicates current location 42 | /// as a node index, the set of open valves, the amount of time remaining, and 43 | /// the total pressure that will be released once time runs out given the valves 44 | /// open. 45 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 46 | pub struct TravelState { 47 | pub location: usize, 48 | pub valves_open: u64, 49 | pub remaining: u32, 50 | pub released: u32, 51 | } 52 | 53 | impl Default for TravelState { 54 | fn default() -> Self { 55 | TravelState { 56 | location: Default::default(), 57 | valves_open: Default::default(), 58 | remaining: 30, // Default to 30 minutes remaining 59 | released: Default::default(), 60 | } 61 | } 62 | } 63 | 64 | impl TravelState { 65 | /// Return a list of possible next states that can be reached from the 66 | /// current state. 67 | pub fn next_states(&self, map: &ValveMap) -> Vec { 68 | let mut out = Vec::new(); 69 | 70 | // For each neighboring valve (and the distance to get to it)... 71 | for (neighbor, distance) in map.edges[self.location].iter() { 72 | // If the distance to the neighbor is to far to reach in the 73 | // time remaining or that valve is already open, then skip that 74 | // neighbor as a possible next state. 75 | if *distance >= self.remaining { 76 | continue; 77 | } 78 | let valve = map.nodes[*neighbor]; 79 | if valve.id & self.valves_open > 0 { 80 | continue; 81 | } 82 | 83 | // Update the next state by subtracting the distance traveled from the 84 | // time remaining, with one extra for opening the destination valve. 85 | // Set the location of the next state to the neighbor index and add 86 | // the set bit for the valve ID to the value keeping track of which 87 | // valves are already open. When opening a new valve, add the flow rate 88 | // of that valve times the remaining time to the total pressure released 89 | // by this state, since we already know that valve will remain open 90 | // for the rest of the time, releasing its flow rate each minute. 91 | let mut new_state = *self; 92 | new_state.location = *neighbor; 93 | new_state.valves_open |= valve.id; 94 | new_state.remaining -= (distance + 1); 95 | new_state.released += (new_state.remaining * valve.flow); 96 | out.push(new_state); 97 | } 98 | 99 | // If there aren't any neighbors to travel to, either because all the valves 100 | // are already open or the travel time is more than time remaining, then we 101 | // can just "wait it out", staying put until time runs out. 102 | if out.is_empty() { 103 | let mut wait_state = *self; 104 | wait_state.remaining = 0; 105 | out.push(wait_state) 106 | } 107 | 108 | out 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/day16/part2.rs: -------------------------------------------------------------------------------- 1 | use super::part1::TravelState; 2 | use crate::day16::{Input, Output, ValveMap}; 3 | 4 | /// Solve Day 16, Part 2 5 | /// 6 | /// This part is also a bit hacky. Here we're again producing every possible end 7 | /// state from walking through and opening valves, just in 26 minutes instead of 8 | /// 30. With that list of possible paths through the valves, we check the paths 9 | /// that release the most pressure for two that open different sets of valves. 10 | /// The total pressure released from the most pressure-releasing pair of 11 | /// non-overlapping paths is the most pressure that two actors can release working 12 | /// together. 13 | pub fn solve(input: &Input) -> Output { 14 | // Now the initial state starts with only 26 minutes remaining. 15 | let mut state = TravelState { 16 | remaining: 26, 17 | ..Default::default() 18 | }; 19 | let mut open = vec![state]; 20 | 21 | // Instead of tracking the most pressure released, we're collecting all possible 22 | // final states. 23 | let mut best_results = Vec::new(); 24 | while let Some(state) = open.pop() { 25 | if state.remaining == 0 { 26 | best_results.push(state); 27 | continue; 28 | } 29 | 30 | for neighbor in state.next_states(input) { 31 | open.push(neighbor); 32 | } 33 | } 34 | 35 | // Now we sort the final states by pressure released 36 | best_results.sort_unstable(); 37 | 38 | // How many of the most productive states should be considered? This is the 39 | // 1337 hax. If we don't check enough of the most productive states, we'll 40 | // get the wrong answer. So, how many should we check? Enough until we get the 41 | // right answer! Honestly I started high and just started reducing the number 42 | // until I got low enough that this function started producing the wrong 43 | // answer. 44 | let depth = 285; 45 | 46 | // Notice that we're iterating backwards over the list of results, since 47 | // it's sorted by ascending total pressure released. For each top result, we 48 | // check down the list for the next result that doesn't overlap. There's a 49 | // possibility that this wouldn't work and we'd need to find a few pairs 50 | // this way and get their maximum, but for my input the first pair found 51 | // through this strategy produces the correct result. 52 | for (idx, result1) in best_results.iter().rev().enumerate().take(depth) { 53 | for result2 in best_results.iter().rev().skip(idx + 1).take(depth) { 54 | if result1.valves_open & result2.valves_open > 0 { 55 | continue; 56 | } 57 | let max = result1.released + result2.released; 58 | return max.into(); 59 | } 60 | } 61 | 62 | // If we get this far, we've failed. 63 | panic!("Could not solve part two!"); 64 | } 65 | 66 | /// Implement ordering for travel states so we can sort the list above. We'll 67 | /// sort by total pressure released. 68 | impl Ord for TravelState { 69 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 70 | self.released.cmp(&other.released) 71 | } 72 | } 73 | 74 | impl PartialOrd for TravelState { 75 | fn partial_cmp(&self, other: &Self) -> Option { 76 | Some(self.cmp(other)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/day17/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day17::Input; 2 | 3 | /// Represents a gust from the jets of gas, either to the left or right. 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum Gust { 6 | Left, 7 | Right, 8 | } 9 | 10 | /// An iterator that produces gusts of gas in an repeating cycle 11 | #[derive(Debug, Clone)] 12 | pub struct GasJetIter { 13 | iter: Vec, 14 | pub idx: usize, 15 | } 16 | 17 | /// Iterator implementation for `GasJetIter`. Just returns the values contained 18 | /// in a repeating cycle. 19 | impl Iterator for GasJetIter { 20 | type Item = Gust; 21 | 22 | fn next(&mut self) -> Option { 23 | let result = Some(self.iter[self.idx]); 24 | self.idx = (self.idx + 1) % self.iter.len(); 25 | result 26 | } 27 | } 28 | 29 | /// Convert a list of `Gust`s into a `GasJetIter` 30 | impl From> for GasJetIter { 31 | fn from(iter: Vec) -> Self { 32 | GasJetIter { 33 | iter, 34 | idx: Default::default(), 35 | } 36 | } 37 | } 38 | 39 | /// Module to wrap the parsing functions for today's puzzle 40 | mod parser { 41 | use super::*; 42 | use anyhow::{anyhow, Result}; 43 | use nom::{ 44 | branch::alt, 45 | character::complete::char, 46 | combinator::{map, value}, 47 | multi::many1, 48 | Finish, IResult, 49 | }; 50 | 51 | /// Nom parser for '<' -> Gust::Left 52 | fn push_left(s: &str) -> IResult<&str, Gust> { 53 | value(Gust::Left, char('<'))(s) 54 | } 55 | 56 | /// Nom parser for '>' -> Gust::Right 57 | fn push_right(s: &str) -> IResult<&str, Gust> { 58 | value(Gust::Right, char('>'))(s) 59 | } 60 | 61 | /// Nom parser for a Gust in either direction 62 | fn push(s: &str) -> IResult<&str, Gust> { 63 | alt((push_left, push_right))(s) 64 | } 65 | 66 | /// Nom parser for a list of Gusts 67 | fn pushes(s: &str) -> IResult<&str, Vec> { 68 | many1(push)(s) 69 | } 70 | 71 | /// Nom parser to map a GasJetIter to a list of Gusts 72 | fn gas_jet_iter(s: &str) -> IResult<&str, GasJetIter> { 73 | map(pushes, GasJetIter::from)(s) 74 | } 75 | 76 | /// Main parsing function, attempts to parse the input into a GasJetIter and 77 | /// return it. 78 | pub fn parse(s: &str) -> Result { 79 | let (_, result) = gas_jet_iter(s).finish().map_err(|e| anyhow!("{e}"))?; 80 | Ok(result) 81 | } 82 | } 83 | 84 | const INPUT: &str = include_str!("../../input/17/input.txt"); 85 | 86 | /// Parse that input! 87 | pub fn read() -> Input { 88 | parser::parse(INPUT).unwrap() 89 | } 90 | 91 | #[cfg(test)] 92 | mod test { 93 | use super::*; 94 | 95 | impl GasJetIter { 96 | fn len(&self) -> usize { 97 | self.iter.len() 98 | } 99 | } 100 | 101 | #[test] 102 | fn check_input() { 103 | let input = read(); 104 | assert_eq!(input.len(), 10091); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/day17/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{GasJetIter, Gust}; 7 | 8 | pub type Input = GasJetIter; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 3147); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 1532163742758u64); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day17/part2.rs: -------------------------------------------------------------------------------- 1 | use super::part1::{Chamber, Rock}; 2 | use crate::day17::{Input, Output}; 3 | use std::collections::HashMap; 4 | 5 | /// Solve Day 17, Part 2 6 | pub fn solve(input: &Input) -> Output { 7 | // We need an owned copy of the iterator so we can use it in both parts 8 | let mut gas_jets = input.to_owned(); 9 | let total_rocks = 1_000_000_000_000; // That's a _lot_ of rocks... 10 | let mut rocks_added = 0; // Number of rocks added to the chamber 11 | 12 | // Keep up with the states we've seen of the chamber before. The state includes 13 | // the top 8 levels of the chamber, the shape of the last rock added to the 14 | // chamber, and the current internal index of the `gas_jets`. 15 | let mut seen = HashMap::with_capacity(2048); 16 | 17 | // Why use 2048 as the capacity now? It's larger than the repeat length of the 18 | // chamber. What repeat length? Well, as it turns out, typically when Advent of 19 | // Code asks you what the state of some data structure will be one bazillion 20 | // iterations later, there's probably a cycle in the state of the data structure 21 | // somewhere that can be used to calculate the final state without needing to 22 | // simulate each step. In my case, the state of the top layers of the chamber 23 | // repeated every 1700 rocks dropped or so. 24 | let mut chamber = Chamber::with_capacity(2048); 25 | 26 | // How much height has been accumulated so far? 27 | let mut accumulated_height = 0; 28 | 29 | // An iterator to produce rocks of the appropriate shape in a cycle. 30 | let mut rock_types = Rock::all().into_iter().cycle(); 31 | 32 | // Until we've added all gajillion rocks... 33 | while rocks_added < total_rocks { 34 | // Get the next rock, add it to the chamber, account for it, and if the 35 | // chamber has fewer than 8 levels, do it again. We're checking the state 36 | // based on the top 8 levels, so no checking until we have at least 8 levels. 37 | let rock = rock_types.next().unwrap(); 38 | chamber.add_rock(&mut gas_jets, rock); 39 | rocks_added += 1; 40 | if chamber.height() < 8 { 41 | continue; 42 | } 43 | 44 | // Check to see if we've seen this state of the top 8 levels of the chamber 45 | // before. If so, time to use that cycle! 46 | let state = (chamber.skyline(), rock, gas_jets.idx); 47 | if let Some((prev_rocks_added, prev_height)) = seen.get(&state) { 48 | // The number of rocks added in each repeating cycle. 49 | let repeat_len: usize = rocks_added - prev_rocks_added; 50 | 51 | // The number of repeats left before we add the final rock. 52 | let repeats: usize = (total_rocks - rocks_added) / repeat_len; 53 | 54 | // Add all the rocks in all the repeating cycles between here and the end. 55 | rocks_added += repeat_len * repeats; 56 | 57 | // Add the chamber height of the cycle to the accumulated height 58 | accumulated_height += repeats * (chamber.height() - prev_height); 59 | 60 | // Clear the map of seen states. We don't want to do everything inside 61 | // this `if` block again on the next iteration, after all. 62 | seen.clear(); 63 | continue; 64 | } 65 | 66 | // If we haven't seen this state before, add it to the map and keep going. 67 | seen.insert(state, (rocks_added, chamber.height())); 68 | } 69 | 70 | // Report the current height of the chamber and the accumulated height from all 71 | // the cycles. The chamber will contain all the rocks dropped up to the start 72 | // of the second repetition of the cycle, then all the rocks that would be 73 | // dropped after the last full cycle ended. 74 | (chamber.height() as u64 + accumulated_height as u64).into() 75 | } 76 | 77 | impl Chamber { 78 | /// Get the 'skyline' of the top of the chamber. Really, it's just a u64 with 79 | /// bits representing the top 8 levels of the chamber. 80 | fn skyline(&self) -> Option { 81 | // If the chamber is less than 8 levels tall, we can't take a skyline 82 | if self.height() < 8 { 83 | return None; 84 | } 85 | 86 | // Take the top 8 levels of the chamber and fold them into a 64-bit integer. 87 | let result = self 88 | .0 89 | .iter() 90 | .rev() 91 | .take(8) 92 | .fold(0u64, |acc, byte| (acc << 8) | *byte as u64); 93 | Some(result) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/day18/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day18::Input; 2 | 3 | /// Represents a 1x1x1 cube in 3D space 4 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] 5 | pub struct Cube(i32, i32, i32); 6 | 7 | // Just some convenience functions for working with Cubes 8 | impl Cube { 9 | pub fn new(x: i32, y: i32, z: i32) -> Self { 10 | Cube(x, y, z) 11 | } 12 | 13 | pub fn inner(&self) -> (i32, i32, i32) { 14 | let Cube(x, y, z) = self; 15 | (*x, *y, *z) 16 | } 17 | } 18 | 19 | /// Convert a tuple of i32s into a Cube 20 | impl From<(i32, i32, i32)> for Cube { 21 | fn from(value: (i32, i32, i32)) -> Self { 22 | let (x, y, z) = value; 23 | Cube(x, y, z) 24 | } 25 | } 26 | 27 | /// Module wrapping the parsing functions for today's puzzle input 28 | mod parser { 29 | use super::*; 30 | use anyhow::{anyhow, Result}; 31 | use nom::{ 32 | branch::alt, 33 | bytes::complete::tag, 34 | character::complete::{i32, newline}, 35 | combinator::map, 36 | multi::separated_list0, 37 | sequence::{terminated, tuple}, 38 | Finish, IResult, 39 | }; 40 | use std::collections::HashSet; 41 | 42 | /// Nom parser for ("5," || "5") -> 5i32 43 | fn number(s: &str) -> IResult<&str, i32> { 44 | alt((terminated(i32, tag(",")), i32))(s) 45 | } 46 | 47 | /// Nom parser for "1,2,3" -> (1, 2, 3) 48 | fn coordinates(s: &str) -> IResult<&str, (i32, i32, i32)> { 49 | // Using tuple instead of separated_list1 here because I want 50 | // to avoid allocating the Vec 51 | tuple((number, number, number))(s) 52 | } 53 | 54 | /// Nom parser for "1,2,3" -> Cube(1, 2, 3) 55 | fn cube(s: &str) -> IResult<&str, Cube> { 56 | map(coordinates, Cube::from)(s) 57 | } 58 | 59 | /// Parses a list of input lines into a list of Cubes 60 | fn cubes(s: &str) -> IResult<&str, Vec> { 61 | separated_list0(newline, cube)(s) 62 | } 63 | 64 | /// Parses the input file into a HashSet of Cubes 65 | pub fn parse(s: &str) -> Result> { 66 | let (_, result) = cubes(s).finish().map_err(|e| anyhow!("{e}"))?; 67 | Ok(result.into_iter().collect::>()) 68 | } 69 | } 70 | 71 | const INPUT: &str = include_str!("../../input/18/input.txt"); 72 | 73 | /// Parse that input! 74 | pub fn read() -> Input { 75 | parser::parse(INPUT).unwrap() 76 | } 77 | 78 | #[cfg(test)] 79 | mod test { 80 | use super::parser::*; 81 | use super::*; 82 | 83 | #[test] 84 | fn check_input() { 85 | let input = read(); 86 | assert_eq!(input.len(), 2881); 87 | assert!(input.contains(&Cube(16, 8, 6))); 88 | assert!(input.contains(&Cube(8, 20, 10))); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/day18/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::Cube; 7 | use std::collections::HashSet; 8 | 9 | pub type Input = HashSet; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 4308); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 2540); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day18/part1.rs: -------------------------------------------------------------------------------- 1 | use super::input::Cube; 2 | use crate::day18::{Input, Output}; 3 | use std::collections::HashSet; 4 | use std::ops::Add; 5 | 6 | /// Solve Day 18, Part 1 7 | pub fn solve(input: &Input) -> Output { 8 | let mut surface_area = 0; 9 | 10 | // For each cube in the input, look at each cube that shares 11 | // a face with it. For every one of those neighboring cubes that 12 | // is not lava, add one to the total surface area. 13 | for cube in input.iter() { 14 | for neighbor in cube.neighbors() { 15 | if !input.contains(&neighbor) { 16 | surface_area += 1; 17 | } 18 | } 19 | } 20 | 21 | surface_area.into() 22 | } 23 | 24 | /// Represents an offset used to adjust a Cube location 25 | #[derive(Debug, Clone, Copy)] 26 | pub struct Offset(i32, i32, i32); 27 | 28 | impl Offset { 29 | /// All the offsets of interest. Provides the offsets that will result 30 | /// in a Cube that shares a face with an existing Cube. 31 | const fn all() -> [Offset; 6] { 32 | [ 33 | Offset(-1, 0, 0), 34 | Offset(1, 0, 0), 35 | Offset(0, -1, 0), 36 | Offset(0, 1, 0), 37 | Offset(0, 0, -1), 38 | Offset(0, 0, 1), 39 | ] 40 | } 41 | 42 | /// Convenience! 43 | fn new(x: i32, y: i32, z: i32) -> Self { 44 | Offset(x, y, z) 45 | } 46 | 47 | /// Convenience! 48 | fn inner(&self) -> (i32, i32, i32) { 49 | let Offset(x, y, z) = self; 50 | (*x, *y, *z) 51 | } 52 | } 53 | 54 | /// Allows for adding an Offset to a Cube to get a Cube offset from the original 55 | impl Add for Cube { 56 | type Output = Cube; 57 | 58 | fn add(self, offset: Offset) -> Self::Output { 59 | let (cx, cy, cz) = self.inner(); 60 | let (ox, oy, oz) = offset.inner(); 61 | let x = cx + ox; 62 | let y = cy + oy; 63 | let z = cz + oz; 64 | 65 | Cube::new(x, y, z) 66 | } 67 | } 68 | 69 | impl Cube { 70 | /// Get all the neighboring Cubes that share a face with this one. 71 | pub fn neighbors(&self) -> [Cube; 6] { 72 | let mut cubes = [Cube::default(); 6]; 73 | let offsets = Offset::all(); 74 | for (slot, offset) in cubes.iter_mut().zip(offsets.iter()) { 75 | *slot = *self + *offset; 76 | } 77 | cubes 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/day18/part2.rs: -------------------------------------------------------------------------------- 1 | use super::input::Cube; 2 | use super::part1::Offset; 3 | use crate::day18::{Input, Output}; 4 | use std::collections::HashSet; 5 | use std::ops::RangeInclusive; 6 | 7 | /// Solve Day 18, Part 2 8 | pub fn solve(input: &Input) -> Output { 9 | // Identify the bounding box that contains all the Cubes for the lava 10 | // plus at least one extra in each dimension. 11 | let bounds = input.get_bounds(); 12 | 13 | // Adding up surface area, like before 14 | let mut surface_area = 0; 15 | 16 | // Prepare for a Depth-First Search through all the Cubes that are within 17 | // the bounding box but that _aren't_ lava. Each "air" cube that touches 18 | // a lava cube adds one to the total observed surface area. 19 | let mut stack = Vec::with_capacity(input.len() * 2); 20 | let mut seen = HashSet::with_capacity(input.len() * 2); 21 | 22 | // This isn't technically a corner of the bounding box or anything, but 23 | // I checked my input and there isn't any lava at this location. It really 24 | // doesn't matter _where_ you start the search, so long as it's a Cube 25 | // outside the lava blob. 26 | let start = Cube::new(0, 0, 0); 27 | stack.push(start); 28 | 29 | // So long as there are still Cubes outside the lava blob to check... 30 | while let Some(cube) = stack.pop() { 31 | // If the cube we're on has already been explored or it falls outside 32 | // the bounding box, skip it. 33 | if seen.contains(&cube) || !bounds.contains(&cube) { 34 | continue; 35 | } 36 | 37 | // If the cube we're on contains lava, then we add one to our surface area 38 | // and move on. We don't want to explore the lava-containing cubes. 39 | if input.contains(&cube) { 40 | surface_area += 1; 41 | continue; 42 | } 43 | 44 | seen.insert(cube); 45 | 46 | // For each cube that shares a face with the current Cube, if we haven't 47 | // explored it already, add it to the stack for later exploration. 48 | for neighbor in cube.neighbors() { 49 | if seen.contains(&neighbor) { 50 | continue; 51 | } 52 | stack.push(neighbor); 53 | } 54 | } 55 | 56 | surface_area.into() 57 | } 58 | 59 | /// Represents the 3D range that contains all the air Cubes we want to explore, 60 | /// encapsulating the lava Cubes. 61 | struct Bounds( 62 | RangeInclusive, 63 | RangeInclusive, 64 | RangeInclusive, 65 | ); 66 | 67 | /// This trait provides a convenient way to get the bounding box of 68 | /// a HashSet of Cubes 69 | trait GetBounds { 70 | fn get_bounds(&self) -> Bounds; 71 | } 72 | 73 | impl GetBounds for &HashSet { 74 | /// Nothing fancy here. Identify the minimim and maximum values on each axis 75 | /// for all the lava-containing cubes, then set the bounding box to be one 76 | /// greater in each dimension. This leaves a one-wide gap around the lava 77 | /// blob that is OK to explore. 78 | fn get_bounds(&self) -> Bounds { 79 | let (mut min_x, mut max_x) = (0, 0); 80 | let (mut min_y, mut max_y) = (0, 0); 81 | let (mut min_z, mut max_z) = (0, 0); 82 | 83 | for cube in self.iter() { 84 | let (x, y, z) = cube.inner(); 85 | min_x = min_x.min(x); 86 | max_x = max_x.max(x); 87 | min_y = min_y.min(y); 88 | max_y = max_y.max(y); 89 | min_z = min_z.min(z); 90 | max_z = max_z.max(z); 91 | } 92 | 93 | let x_range = (min_x - 1)..=(max_x + 1); 94 | let y_range = (min_y - 1)..=(max_y + 1); 95 | let z_range = (min_z - 1)..=(max_z + 1); 96 | 97 | Bounds(x_range, y_range, z_range) 98 | } 99 | } 100 | 101 | impl Bounds { 102 | /// Indicates whether a Cube lies within the Bounds 103 | fn contains(&self, cube: &Cube) -> bool { 104 | let (x, y, z) = cube.inner(); 105 | let Bounds(x_range, y_range, z_range) = self; 106 | x_range.contains(&x) && y_range.contains(&y) && z_range.contains(&z) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/day19/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::Blueprint; 7 | 8 | pub type Input = Vec; 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 1177); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 62744); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day19/part2.rs: -------------------------------------------------------------------------------- 1 | use super::part1::Factory; 2 | use crate::day19::{Input, Output}; 3 | use rayon::prelude::*; 4 | 5 | /// Solve Day 19, Part 2 6 | pub fn solve(input: &Input) -> Output { 7 | // That's right, it's basically the same as part one, with the slight 8 | // modifications that we're only examining the first three blueprints, 9 | // keeping the number of geodes per Blueprint as opposed to the quality 10 | // level, and multiplying instead of adding the results. 11 | input 12 | .par_iter() 13 | .take(3) 14 | .map(|blueprint| Factory::new(*blueprint, 32)) 15 | .map(|factory| factory.geodes_produced()) 16 | .product::() 17 | .into() 18 | } 19 | -------------------------------------------------------------------------------- /src/day20/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day20::Input; 2 | 3 | const INPUT: &str = include_str!("../../input/20/input.txt"); 4 | 5 | pub fn read() -> Input { 6 | INPUT.lines().flat_map(|l| l.parse::()).collect() 7 | } 8 | 9 | #[cfg(test)] 10 | mod test { 11 | use super::*; 12 | use std::collections::HashSet; 13 | 14 | #[test] 15 | fn check_input() { 16 | let input = read(); 17 | assert_eq!(input.len(), 5000); 18 | assert_eq!(input[0], -8023); 19 | assert_eq!(input[4999], 5838); 20 | 21 | let input_set = input.iter().collect::>(); 22 | assert_eq!(input.len(), input_set.len()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/day20/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | 7 | pub type Input = Vec; 8 | 9 | pub fn run(part: Part) -> Output { 10 | let input = input::read(); 11 | match part { 12 | Part::One => part1::solve(&input), 13 | Part::Two => part2::solve(&input), 14 | } 15 | } 16 | 17 | pub fn run_both() -> (Output, Output) { 18 | let input = input::read(); 19 | (part1::solve(&input), part2::solve(&input)) 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn check_answer_one() { 28 | let result = run(Part::One); 29 | assert_eq!(result, 19070); 30 | } 31 | 32 | #[test] 33 | fn check_answer_two() { 34 | let result = run(Part::Two); 35 | assert_eq!(result, 14773357352059u64); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/day20/part2.rs: -------------------------------------------------------------------------------- 1 | use super::part1::MixingDecryptor; 2 | use crate::day20::{Input, Output}; 3 | 4 | /// Solve Day 20, Part 2 5 | /// 6 | /// No real changes here, other than multiplying each value by some hefty 7 | /// prime-looking number and mixing the list ten times. Just call me 8 | /// Sir Mix-A-Lot. 9 | pub fn solve(input: &Input) -> Output { 10 | let mod_input: Vec<_> = input.iter().map(|x| x * 811589153).collect(); 11 | let mut decryptor = MixingDecryptor::from(mod_input); 12 | (0..10).for_each(|_| decryptor.mix()); 13 | decryptor.grove_coordinates_sum().into() 14 | } 15 | -------------------------------------------------------------------------------- /src/day21/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::Environment; 7 | use std::collections::HashMap; 8 | 9 | pub type Input = Environment; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | println!("{result}"); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | println!("{result}"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day21/part1.rs: -------------------------------------------------------------------------------- 1 | use super::input::{Environment, Expression, Label, Value}; 2 | use crate::day21::{Input, Output}; 3 | 4 | /// Solve Day 21, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | let root = Value::try_from("root").unwrap(); 7 | root.eval(input).unwrap().into() 8 | } 9 | 10 | /// Fetch an Expression from the Environment by Label 11 | impl Environment { 12 | pub fn resolve(&self, var: &Label) -> Option { 13 | self.0.get(var).copied() 14 | } 15 | } 16 | 17 | /// Trait for evaluating an Expression in an Environment 18 | trait Eval { 19 | fn eval(&self, env: &Environment) -> Option; 20 | } 21 | 22 | impl Eval for Value { 23 | /// Recursively evaluate the Value until a Value::Raw can be returned 24 | fn eval(&self, env: &Environment) -> Option { 25 | match self { 26 | Value::Ref(var) => env.resolve(var).and_then(|v| v.eval(env)), 27 | Value::Raw(val) => Some(*val), 28 | } 29 | } 30 | } 31 | 32 | impl Eval for Expression { 33 | /// Recursively evaluate the Value until a Value::Raw can be returned 34 | fn eval(&self, env: &Environment) -> Option { 35 | // Perform operations based on the type of Expression 36 | match self { 37 | Expression::Add(v1, v2) => Some(v1.eval(env)? + v2.eval(env)?), 38 | Expression::Sub(v1, v2) => Some(v1.eval(env)? - v2.eval(env)?), 39 | Expression::Mul(v1, v2) => Some(v1.eval(env)? * v2.eval(env)?), 40 | Expression::Div(v1, v2) => Some(v1.eval(env)? / v2.eval(env)?), 41 | Expression::Val(value) => value.eval(env), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/day22/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::{Direction, MonkeyMap}; 7 | 8 | pub type Input = (MonkeyMap, Vec); 9 | 10 | pub fn run(part: Part) -> Output { 11 | let input = input::read(); 12 | match part { 13 | Part::One => part1::solve(&input), 14 | Part::Two => part2::solve(&input), 15 | } 16 | } 17 | 18 | pub fn run_both() -> (Output, Output) { 19 | let input = input::read(); 20 | (part1::solve(&input), part2::solve(&input)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn check_answer_one() { 29 | let result = run(Part::One); 30 | assert_eq!(result, 13566); 31 | } 32 | 33 | #[test] 34 | fn check_answer_two() { 35 | let result = run(Part::Two); 36 | assert_eq!(result, 11451); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/day22/part1.rs: -------------------------------------------------------------------------------- 1 | use super::input::{Direction, Heading, Links, MonkeyMap, Position, Tile}; 2 | use crate::day22::{Input, Output}; 3 | 4 | /// Solve Day 22, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | // This bit is just so I can convert the reference to the input into a 7 | // mutable map. I pass in input as a reference because it keeps me from 8 | // accidentally mutating it in Part 1 and spending hours wondering why 9 | // my solution for Part 2 doesn't work on the input I forgot I had mutated. 10 | let (monkey_map, directions) = input; 11 | let mut board = monkey_map.clone(); 12 | 13 | // Start at the first path Tile on the first row, facing right 14 | let Some(start_pos) = board.first_path_position() else { panic!("Cannot find start position!"); }; 15 | let mut walker = Walker::new(start_pos); 16 | 17 | // Follow each direction 18 | directions 19 | .iter() 20 | .for_each(|direction| walker.follow(&board, *direction)); 21 | 22 | // Let the walker calculate its own score and return it 23 | walker.score().into() 24 | } 25 | 26 | impl MonkeyMap { 27 | /// Finds the position of the first Tile::Path in the MonkeyMap, 28 | /// in reading order (left to right, top to bottom), if there is 29 | /// one. 30 | pub fn first_path_position(&self) -> Option { 31 | let first_row = self.0.first()?; 32 | let first_col = first_row 33 | .iter() 34 | .position(|tile| matches!(tile, Tile::Path(_)))?; 35 | Some(Position(0, first_col)) 36 | } 37 | } 38 | 39 | impl Tile { 40 | /// Get the links associated with the Tile. If it's a Tile::Void 41 | /// or Tile::Wall, the default is a Links with no links filled in. 42 | /// That doesn't actually come up much, though. 43 | pub fn links(&self) -> Links { 44 | match self { 45 | Tile::Path(links) => *links, 46 | _ => Links::default(), 47 | } 48 | } 49 | } 50 | 51 | /// Represents a "walker" walking the path on the MonkeyMap. 52 | #[derive(Debug)] 53 | pub struct Walker(pub Heading, pub Position); 54 | 55 | impl Walker { 56 | pub fn new(position: Position) -> Self { 57 | Walker(Heading::Right, position) 58 | } 59 | 60 | /// Lets the Walker calculate it's own score according to the puzzle description. 61 | pub fn score(&self) -> u32 { 62 | let Walker(heading, position) = self; 63 | let Position(row, col) = position; 64 | let heading_mod = match heading { 65 | Heading::Right => 0, 66 | Heading::Down => 1, 67 | Heading::Left => 2, 68 | Heading::Up => 3, 69 | }; 70 | ((*row as u32 + 1) * 1000) + ((*col as u32 + 1) * 4) + heading_mod 71 | } 72 | 73 | /// Given a direction and a reference to the map, attempt to follow the 74 | /// direction by moving or turning the Walker. 75 | pub fn follow(&mut self, map: &MonkeyMap, direction: Direction) { 76 | let Walker(heading, position) = self; 77 | let tile = map[*position]; 78 | let Tile::Path(links) = tile else { return; }; 79 | 80 | use Direction::*; 81 | use Heading::*; 82 | match (heading, direction) { 83 | // Turning is easy, just match and update the Walker with the new heading 84 | (Up, TurnLeft) => *self = Walker(Left, *position), 85 | (Up, TurnRight) => *self = Walker(Right, *position), 86 | 87 | (Right, TurnLeft) => *self = Walker(Up, *position), 88 | (Right, TurnRight) => *self = Walker(Down, *position), 89 | 90 | (Down, TurnLeft) => *self = Walker(Right, *position), 91 | (Down, TurnRight) => *self = Walker(Left, *position), 92 | 93 | (Left, TurnLeft) => *self = Walker(Down, *position), 94 | (Left, TurnRight) => *self = Walker(Up, *position), 95 | 96 | // Moving forward is a bit tougher, but not too serious. 97 | (heading, Forward(n)) => { 98 | // As many times as we're suppose to move forward... 99 | for _ in 0..n { 100 | // Unpack the walker again since we're going to modify it 101 | // directly in subsequent loops. 102 | let Walker(heading, position) = self; 103 | 104 | // Here's where having the Links stored on the Tiles comes in 105 | // handy. We can just query the links on the current Tile for 106 | // the heading and position we should be at if we move in the 107 | // indicated direction from the current Tile. 108 | let mut links = map[*position].links(); 109 | let link = match heading { 110 | Up => links.up, 111 | Right => links.right, 112 | Down => links.down, 113 | Left => links.left, 114 | }; 115 | 116 | // If there's no link in the indicated direction, just stop. 117 | // Otherwise, update the Walker and keep going. 118 | let Some((heading, position)) = link else { break; }; 119 | *self = Walker(heading, position) 120 | } 121 | } 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/day23/input.rs: -------------------------------------------------------------------------------- 1 | use super::grid::Grid; 2 | use crate::day23::Input; 3 | use std::str::FromStr; 4 | 5 | const INPUT: &str = include_str!("../../input/23/input.txt"); 6 | 7 | /// Parse the input! 8 | pub fn read() -> Input { 9 | Grid::from_str(INPUT).unwrap() 10 | } 11 | -------------------------------------------------------------------------------- /src/day23/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod grid; 2 | pub mod input; 3 | pub mod part1; 4 | pub mod part2; 5 | 6 | use crate::{Output, Part}; 7 | use grid::Grid; 8 | 9 | // These values are tuned to produce the right answer in the least 10 | // time. It's a bit of a hack, to be honest, but it works! 11 | pub type Input = Grid; 12 | 13 | pub fn run(part: Part) -> Output { 14 | let input = input::read(); 15 | match part { 16 | Part::One => part1::solve(&input), 17 | Part::Two => part2::solve(&input), 18 | } 19 | } 20 | 21 | pub fn run_both() -> (Output, Output) { 22 | let input = input::read(); 23 | (part1::solve(&input), part2::solve(&input)) 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn check_answer_one() { 32 | let result = run(Part::One); 33 | assert_eq!(result, 4052); 34 | } 35 | 36 | #[test] 37 | fn check_answer_two() { 38 | let result = run(Part::Two); 39 | assert_eq!(result, 978); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/day23/part1.rs: -------------------------------------------------------------------------------- 1 | use super::grid::{Grid, GridBuilder, Rules}; 2 | use crate::day23::{Input, Output}; 3 | use std::collections::HashMap; 4 | 5 | /// Solve Day 23, Part 1 6 | pub fn solve(input: &Input) -> Output { 7 | // Start with a copy of the Grid and the default order of proposal directions 8 | let mut state = *input; 9 | let mut propose_order = Rules::default(); 10 | 11 | // Update the Grid ten times. Still not convinced that I _needed_ the builder 12 | // pattern here, but it does make for a nice syntax for producing new Grid 13 | // states. It's nice to have listed each part of the process like this. 14 | for _ in 0..10 { 15 | state = GridBuilder::init(state, propose_order) 16 | .identify_movers() 17 | .make_proposals() 18 | .resolve_conflicts() 19 | .finalize(); 20 | propose_order.rotate(); 21 | } 22 | 23 | // Count and return the number of empty spaces 24 | state.count_empty_spaces().into() 25 | } 26 | -------------------------------------------------------------------------------- /src/day23/part2.rs: -------------------------------------------------------------------------------- 1 | use super::grid::{Grid, GridBuilder, Rules}; 2 | use crate::day23::{Input, Output}; 3 | 4 | /// Solve Day 23, Part 2 5 | pub fn solve(input: &Input) -> Output { 6 | // Start with a copy of the Grid, the initial proposal order, and a blank Grid 7 | let mut state = *input; 8 | let mut last_state = Grid::default(); 9 | let mut propose_order = Rules::default(); 10 | let mut rounds = 0; 11 | 12 | // We'll keep two Grid states around, the current state and the last state. Any 13 | // time the new state matches the old state, we know that no elves moved and 14 | // we can stop. 15 | while state != last_state { 16 | last_state = state; 17 | state = GridBuilder::init(state, propose_order) 18 | .identify_movers() 19 | .make_proposals() 20 | .resolve_conflicts() 21 | .finalize(); 22 | propose_order.rotate(); 23 | rounds += 1; 24 | } 25 | 26 | // Return the number of rounds it took to find a round where no changes 27 | // occurred. 28 | rounds.into() 29 | } 30 | -------------------------------------------------------------------------------- /src/day24/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | pub mod part2; 4 | 5 | use crate::{Output, Part}; 6 | use input::Valley; 7 | use std::collections::HashMap; 8 | 9 | pub type Input = Vec; 10 | 11 | pub fn run(part: Part) -> Output { 12 | let input = input::read(); 13 | match part { 14 | Part::One => part1::solve(&input), 15 | Part::Two => part2::solve(&input), 16 | } 17 | } 18 | 19 | pub fn run_both() -> (Output, Output) { 20 | let input = input::read(); 21 | (part1::solve(&input), part2::solve(&input)) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn check_answer_one() { 30 | let result = run(Part::One); 31 | assert_eq!(result, 283); 32 | } 33 | 34 | #[test] 35 | fn check_answer_two() { 36 | let result = run(Part::Two); 37 | assert_eq!(result, 883); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/day24/part1.rs: -------------------------------------------------------------------------------- 1 | use super::input::{Space, Valley}; 2 | use crate::day24::{Input, Output}; 3 | use core::cmp::Reverse; 4 | use std::collections::{BinaryHeap, HashMap}; 5 | 6 | /// Solve Day 24, Part 1 7 | pub fn solve(input: &Input) -> Output { 8 | let first_state = input.get(0).expect("Valley should have an initial state!"); 9 | 10 | // All examples and input have the start Space at index (0, 1) and the end 11 | // Space on the bottom row, next to the last column. 12 | let start_at = Expedition(0, 1); 13 | let end_at = Expedition(first_state.rows - 1, first_state.cols - 2); 14 | 15 | // Calculate the length of the shortest path through the Valley. 16 | if let Some(minutes) = start_at.shortest_path(end_at, 0, input) { 17 | return (minutes as u32).into(); 18 | } 19 | 20 | // Unless we can't find a path. Then, freak out! This doesn't happen, 21 | // though. Not anymore... 22 | panic!("Could not find a way through the valley. Died of frostbite!"); 23 | } 24 | 25 | /// Represents the location of the elven Expedition through the Valley. 26 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] 27 | pub struct Expedition(pub usize, pub usize); 28 | 29 | impl Expedition { 30 | /// Given the current location of the elves and a state of the Valley, 31 | /// determine which next steps are possible, in order to avoid landing 32 | /// on a Space with an active Blizzard. There are a maximum of five 33 | /// possible moves, including waiting. 34 | fn possible_next_steps(&self, valley: &Valley) -> [Option; 5] { 35 | let Expedition(row, col) = *self; 36 | let mut possible_steps = [None; 5]; 37 | 38 | // Attempt to move to the left 39 | if let Space::Empty = valley.spaces[row][col - 1] { 40 | possible_steps[0] = Some(Expedition(row, col - 1)); 41 | } 42 | 43 | // Attempt to move to the right 44 | if let Space::Empty = valley.spaces[row][col + 1] { 45 | possible_steps[1] = Some(Expedition(row, col + 1)); 46 | } 47 | 48 | // The upward move needs to account for the fact that the 49 | // final space is in the top row, which means that moving into 50 | // the top row is possible. 51 | if row > 0 { 52 | if let Space::Empty = valley.spaces[row - 1][col] { 53 | possible_steps[2] = Some(Expedition(row - 1, col)); 54 | } 55 | } 56 | 57 | // The downard move needs to account for the beginning space 58 | // being on the last row, which means that moving into the last row 59 | // is possible. 60 | if row < (valley.spaces.len() - 1) { 61 | if let Space::Empty = valley.spaces[row + 1][col] { 62 | possible_steps[3] = Some(Expedition(row + 1, col)); 63 | } 64 | } 65 | 66 | // Waiting is a necessary option if there's nothing in our current space 67 | if let Space::Empty = valley.spaces[row][col] { 68 | possible_steps[4] = Some(Expedition(row, col)); 69 | } 70 | 71 | possible_steps 72 | } 73 | 74 | /// Find the shortest path from this Expedition's location to the `target`, 75 | /// assuming we start the journey at minute `start_time`. Pass in a reference 76 | /// to the time states of the Valley so we can know which Spaces are Empty 77 | /// for each minute. It's a Dijkstra's implementation. 78 | pub fn shortest_path( 79 | &self, 80 | target: Expedition, 81 | start_time: usize, 82 | valley_states: &[Valley], 83 | ) -> Option { 84 | // Sort locations in the Heap by the least number of minutes spent. Uniquely 85 | // identify states along the path by the location of the Expedition for a 86 | // given state of the Valley. 87 | let mut open = BinaryHeap::from([(Reverse(start_time), *self)]); 88 | let mut minutes = HashMap::from([((*self, start_time % valley_states.len()), start_time)]); 89 | 90 | // So long as there are states to explore, take the one with the fewest 91 | // number of minutes passed. 92 | while let Some((Reverse(minutes_passed), expedition)) = open.pop() { 93 | // If we've found the end, return the number of minutes passed 94 | if expedition == target { 95 | return Some(minutes_passed); 96 | } 97 | 98 | // Get the state of the Valley in the next minute to identify 99 | // which Spaces are available to be moved to. 100 | let state_idx = (minutes_passed + 1) % valley_states.len(); 101 | let state = &valley_states[state_idx]; 102 | 103 | // Check each next step to see if this is the fastest we've gotten 104 | // to that state. If so, keep it and add it to the heap. Otherwise, 105 | // keep moving on. 106 | for step in expedition.possible_next_steps(state).into_iter().flatten() { 107 | let next_minutes = minutes_passed + 1; 108 | let curr_minutes = *minutes.get(&(step, state_idx)).unwrap_or(&usize::MAX); 109 | if next_minutes >= curr_minutes { 110 | continue; 111 | } 112 | open.push((Reverse(next_minutes), step)); 113 | minutes.insert((step, state_idx), next_minutes); 114 | } 115 | } 116 | 117 | None // Something has gone terribly wrong... 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/day24/part2.rs: -------------------------------------------------------------------------------- 1 | use super::input::Space; 2 | use super::part1::Expedition; 3 | use crate::day24::{Input, Output}; 4 | 5 | /// Solve Day 24, Part 2 6 | pub fn solve(input: &Input) -> Output { 7 | let first_state = input.get(0).expect("Valley should have an initial state!"); 8 | 9 | // All examples and input have the start Space at index (0, 1) and the end 10 | // Space on the bottom row, next to the last column. 11 | let start_at = Expedition(0, 1); 12 | let end_at = Expedition(first_state.rows - 1, first_state.cols - 2); 13 | 14 | // Start at zero minutes and move from the start to the end. 15 | let Some(minutes) = start_at.shortest_path(end_at, 0, input) else { 16 | panic!("Could not find a way through the valley. Died of frostbite!"); 17 | }; 18 | 19 | // Turn around and head back to the start. 20 | let Some(minutes) = end_at.shortest_path(start_at, minutes, input) else { 21 | panic!("Could not find a way back! Died in a blizzard!"); 22 | }; 23 | 24 | // Then turn around and head back to the end again. 25 | let Some(minutes) = start_at.shortest_path(end_at, minutes, input) else { 26 | panic!("Could not find a way back! Died of embarassment!"); 27 | }; 28 | 29 | // Return the total number of minutes it took to travel all that way. 30 | (minutes as u32).into() 31 | } 32 | -------------------------------------------------------------------------------- /src/day25/input.rs: -------------------------------------------------------------------------------- 1 | use crate::day25::Input; 2 | 3 | /// This represents one of our SNAFU numbers, which is just a String 4 | /// in a Wrapper so we can have custom `From` implementations. 5 | #[derive(Debug, Clone)] 6 | pub struct Snafu(pub String); 7 | 8 | /// Converting a line from the input into a Snafu is super complicated. 9 | impl From<&str> for Snafu { 10 | fn from(line: &str) -> Self { 11 | Snafu(line.to_string()) 12 | } 13 | } 14 | 15 | const INPUT: &str = include_str!("../../input/25/input.txt"); 16 | 17 | /// Parse that input! 18 | pub fn read() -> Input { 19 | INPUT.lines().map(Snafu::from).collect::>() 20 | } 21 | -------------------------------------------------------------------------------- /src/day25/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod part1; 3 | 4 | use crate::{Output, Part}; 5 | use input::Snafu; 6 | 7 | pub type Input = Vec; 8 | 9 | pub fn run(part: Part) -> Output { 10 | let input = input::read(); 11 | match part { 12 | Part::One => part1::solve(&input), 13 | Part::Two => String::from("No part 2 for Day 25!").into(), 14 | } 15 | } 16 | 17 | pub fn run_both() -> (Output, Output) { 18 | let input = input::read(); 19 | ( 20 | part1::solve(&input), 21 | Output::String(String::from("Nothing to see here..")), 22 | ) 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn check_answer_one() { 31 | let result = run(Part::One); 32 | assert_eq!(result, "2-=102--02--=1-12=22"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/day25/part1.rs: -------------------------------------------------------------------------------- 1 | use super::input::Snafu; 2 | use crate::day25::{Input, Output}; 3 | 4 | /// Solve Day 25, Part 1 5 | pub fn solve(input: &Input) -> Output { 6 | let fuel_cost: i128 = input.iter().cloned().map(i128::from).sum(); 7 | let snafu_cost = Snafu::from(fuel_cost); 8 | snafu_cost.0.into() 9 | } 10 | 11 | /// Unit conversion from a SNAFU number to a base-10 integer. This is a pretty 12 | /// common algorithm for converting base-whatever to decimal. 13 | impl From for i128 { 14 | fn from(snafu: Snafu) -> Self { 15 | let mut total = 0; 16 | for (pow, glyph) in snafu.0.chars().rev().enumerate() { 17 | let mult = match glyph { 18 | '2' => 2, 19 | '1' => 1, 20 | '0' => 0, 21 | '-' => -1, 22 | '=' => -2, 23 | _ => unreachable!(), 24 | }; 25 | total += 5i128.pow(pow as u32) * mult; 26 | } 27 | total 28 | } 29 | } 30 | 31 | /// Unit conversion from a decimal integer into a SNAFU number. This is a much 32 | /// less common (to me) operation. The tricky bit is handling the fact that we have 33 | /// digits whose value is centered around zero instead of anchored at zero on the 34 | /// least significant place. For a normal base-5 conversion, I'd take the result 35 | /// of the remainder % 5, then divide by 5 for each digit until no remainder was 36 | /// left. Apparently, adding two each round lets us shift the digits. I'm not 37 | /// 100% sure why this works, but it works. Math, amirite? This solution was inspired 38 | /// by Google searches about "balanced ternary" number systems. 39 | impl From for Snafu { 40 | fn from(number: i128) -> Self { 41 | let mut remainder = number; 42 | let mut out = String::default(); 43 | while remainder > 0 { 44 | let glyph = match remainder % 5 { 45 | 0 => '0', 46 | 1 => '1', 47 | 2 => '2', 48 | 3 => '=', 49 | 4 => '-', 50 | _ => unreachable!(), 51 | }; 52 | out.push(glyph); 53 | remainder += 2; // This works for some reason. 54 | remainder /= 5; 55 | } 56 | Snafu(out.chars().rev().collect::()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_intersperse)] 2 | #![allow(unused)] 3 | use std::cmp::PartialEq; 4 | use std::fmt::{Display, Formatter, Result as DisplayResult}; 5 | 6 | pub mod day01; 7 | pub mod day02; 8 | pub mod day03; 9 | pub mod day04; 10 | pub mod day05; 11 | pub mod day06; 12 | pub mod day07; 13 | pub mod day08; 14 | pub mod day09; 15 | pub mod day10; 16 | pub mod day11; 17 | pub mod day12; 18 | pub mod day13; 19 | pub mod day14; 20 | pub mod day15; 21 | pub mod day16; 22 | pub mod day17; 23 | pub mod day18; 24 | pub mod day19; 25 | pub mod day20; 26 | pub mod day21; 27 | pub mod day22; 28 | pub mod day23; 29 | pub mod day24; 30 | pub mod day25; 31 | 32 | // Used as a flag to indicate which part of a day to run. 33 | pub enum Part { 34 | One, 35 | Two, 36 | } 37 | 38 | macro_rules! impl_output_from { 39 | ( $( ($e:tt, $t:ty) ),* ) => { 40 | #[derive(Debug, Eq)] 41 | pub enum Output { 42 | $( $e($t), )* 43 | } 44 | 45 | $( 46 | impl From<$t> for Output { 47 | fn from(value: $t) -> Self { 48 | Output::$e(value) 49 | } 50 | } 51 | )* 52 | }; 53 | } 54 | 55 | impl_output_from! { 56 | (U8, u8), 57 | (U16, u16), 58 | (U32, u32), 59 | (U64, u64), 60 | (U128, u128), 61 | (I8, i8), 62 | (I16, i16), 63 | (I32, i32), 64 | (I64, i64), 65 | (I128, i128), 66 | (String, String) 67 | } 68 | 69 | impl Display for Output { 70 | fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { 71 | match self { 72 | Output::U8(v) => write!(f, "{v}"), 73 | Output::U16(v) => write!(f, "{v}"), 74 | Output::U32(v) => write!(f, "{v}"), 75 | Output::U64(v) => write!(f, "{v}"), 76 | Output::U128(v) => write!(f, "{v}"), 77 | Output::I8(v) => write!(f, "{v}"), 78 | Output::I16(v) => write!(f, "{v}"), 79 | Output::I32(v) => write!(f, "{v}"), 80 | Output::I64(v) => write!(f, "{v}"), 81 | Output::I128(v) => write!(f, "{v}"), 82 | Output::String(v) => write!(f, "{v}"), 83 | } 84 | } 85 | } 86 | 87 | /// Consider an output equal to any value where they can both be 88 | /// coerced to the same string 89 | impl PartialEq for Output { 90 | fn eq(&self, other: &T) -> bool { 91 | *self.to_string() == other.to_string() 92 | } 93 | } 94 | --------------------------------------------------------------------------------