Compare commits

...

34 Commits

Author SHA1 Message Date
00000340e7 solution: day 25 2025-01-02 22:45:07 +01:00
0000033060 solution: day 24 2025-01-02 22:43:58 +01:00
000003208e chore: run cargo fmt 2024-12-23 19:38:33 +01:00
000003103e solution: day 23 2024-12-23 19:38:23 +01:00
0000030073 solution: day 22 2024-12-22 12:41:07 +01:00
00000290eb perf: cache bfs for day 21 2024-12-21 21:29:04 +01:00
0000028054 solution: day 21 2024-12-21 17:51:56 +01:00
00000270f6 solution: day 20 2024-12-21 17:51:15 +01:00
00000260b8 fix: account for wrapping in grid.get and grid.get_mut 2024-12-21 12:47:20 +01:00
000002509e solution: day 19 2024-12-19 21:23:11 +01:00
00000240fc solution: day 18 2024-12-18 22:45:21 +01:00
00000230cf feat: add mul<usize> for direction and diagonal directions 2024-12-18 22:01:22 +01:00
000002201f solution: day 17 2024-12-17 08:28:03 +01:00
000002103d perf: flatten Grid inner repr to Vec<u8> 2024-12-16 21:57:13 +01:00
0000020047 solution: day 16 2024-12-16 21:44:32 +01:00
0000019062 solution: day 15 2024-12-15 20:15:24 +01:00
000001801f solution: day 14 2024-12-15 00:11:58 +01:00
0000017050 solution: day 13 2024-12-13 09:34:43 +01:00
00000160fd solution: day 12 2024-12-12 09:04:31 +01:00
0000015022 solution: day 11 2024-12-11 06:37:27 +01:00
000001408c solution: day 10 2024-12-11 06:37:19 +01:00
0000013050 feat(scaffold): change default return type to Option<usize> 2024-12-09 20:10:30 +01:00
0000012098 solution: day 9 2024-12-09 20:09:35 +01:00
0000011041 solution: day 8 2024-12-08 06:54:33 +01:00
0000010070 solution: day 7 2024-12-08 01:44:19 +01:00
00000090a2 fix: add test values for day 2 2024-12-06 07:20:18 +01:00
000000803c solution: day 6 2024-12-06 07:19:53 +01:00
0000007016 solution: day 5 2024-12-05 07:10:27 +01:00
000000601e solution: day 4 2024-12-04 07:09:45 +01:00
0000005019 solution: day 3 2024-12-03 11:14:00 +01:00
0000004082 solution: day 2 2024-12-02 07:00:45 +01:00
000000305b solution: day 1 2024-12-01 22:51:39 +01:00
000000208a feat: add basic parser functions 2024-12-01 22:49:59 +01:00
00000010b7 chore: add deps 2024-12-01 22:49:26 +01:00
59 changed files with 3422 additions and 12 deletions

296
Cargo.lock generated
View File

@@ -17,9 +17,43 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "aoc"
version = "48.0.0"
dependencies = [
"cached",
"hashbrown 0.15.2",
"itertools",
"num-integer",
"regex",
]
[[package]]
name = "autocfg"
@@ -66,6 +100,39 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cached"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae"
dependencies = [
"ahash",
"cached_proc_macro",
"cached_proc_macro_types",
"hashbrown 0.14.5",
"once_cell",
"thiserror",
"web-time",
]
[[package]]
name = "cached_proc_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cc"
version = "1.0.78"
@@ -94,6 +161,41 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.90",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.90",
]
[[package]]
name = "dotenvy"
version = "0.15.6"
@@ -109,6 +211,12 @@ dependencies = [
"reqwest",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encoding_rs"
version = "0.8.31"
@@ -118,6 +226,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fastrand"
version = "1.8.0"
@@ -133,6 +247,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -236,6 +356,27 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
@@ -316,6 +457,12 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
@@ -333,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@@ -351,6 +498,15 @@ version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.5"
@@ -437,6 +593,24 @@ dependencies = [
"tempfile",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@@ -458,9 +632,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.16.0"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "openssl"
@@ -485,7 +659,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -539,18 +713,18 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@@ -564,6 +738,35 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@@ -723,6 +926,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.107"
@@ -734,6 +943,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
@@ -769,6 +989,26 @@ dependencies = [
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@@ -894,6 +1134,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
version = "0.3.0"
@@ -931,7 +1177,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-shared",
]
@@ -965,7 +1211,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -986,6 +1232,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -1126,3 +1382,23 @@ dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]

View File

@@ -21,4 +21,9 @@ authors.workspace = true
repository.workspace = true
[dependencies]
cached = "0.54.0"
hashbrown = "0.15.2"
itertools = "0.13.0"
num-integer = "0.1.46"
regex = "1.11.1"

6
data/examples/01.txt Normal file
View File

@@ -0,0 +1,6 @@
3 4
4 3
2 5
1 3
3 9
3 3

6
data/examples/02.txt Normal file
View File

@@ -0,0 +1,6 @@
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9

1
data/examples/03.txt Normal file
View File

@@ -0,0 +1 @@
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))

10
data/examples/04.txt Normal file
View File

@@ -0,0 +1,10 @@
MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX

28
data/examples/05.txt Normal file
View File

@@ -0,0 +1,28 @@
47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47

10
data/examples/06.txt Normal file
View File

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

9
data/examples/07.txt Normal file
View File

@@ -0,0 +1,9 @@
190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20

12
data/examples/08.txt Normal file
View File

@@ -0,0 +1,12 @@
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............

1
data/examples/09.txt Normal file
View File

@@ -0,0 +1 @@
2333133121414131402

8
data/examples/10.txt Normal file
View File

@@ -0,0 +1,8 @@
89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732

1
data/examples/11.txt Normal file
View File

@@ -0,0 +1 @@
125 17

10
data/examples/12.txt Normal file
View File

@@ -0,0 +1,10 @@
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE

15
data/examples/13.txt Normal file
View File

@@ -0,0 +1,15 @@
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176
Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450
Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279

12
data/examples/14.txt Normal file
View File

@@ -0,0 +1,12 @@
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3

21
data/examples/15.txt Normal file
View File

@@ -0,0 +1,21 @@
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^

17
data/examples/16.txt Normal file
View File

@@ -0,0 +1,17 @@
#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################

0
data/examples/17.txt Normal file
View File

25
data/examples/18.txt Normal file
View File

@@ -0,0 +1,25 @@
5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0

10
data/examples/19.txt Normal file
View File

@@ -0,0 +1,10 @@
r, wr, b, g, bwu, rb, gb, br
brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb

15
data/examples/20.txt Normal file
View File

@@ -0,0 +1,15 @@
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############

5
data/examples/21.txt Normal file
View File

@@ -0,0 +1,5 @@
029A
980A
179A
456A
379A

4
data/examples/22.txt Normal file
View File

@@ -0,0 +1,4 @@
1
2
3
2024

32
data/examples/23.txt Normal file
View File

@@ -0,0 +1,32 @@
kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn

47
data/examples/24.txt Normal file
View File

@@ -0,0 +1,47 @@
x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1
ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj

39
data/examples/25.txt Normal file
View File

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

57
src/bin/01.rs Normal file
View File

@@ -0,0 +1,57 @@
use hashbrown::HashMap;
use itertools::Itertools;
fn parse_input(input: &str) -> impl Iterator<Item = (isize, isize)> + '_ {
input.lines().map(|l| {
l.split(' ')
.filter_map(|x| x.parse::<isize>().ok())
.collect_tuple()
.unwrap()
})
}
pub fn part_one(input: &str) -> Option<isize> {
let mut left = vec![];
let mut right = vec![];
for (a, b) in parse_input(input) {
left.push(a);
right.push(b);
}
left.sort();
right.sort();
Some(
left.into_iter()
.zip(right)
.map(|(x, y)| (x - y).abs())
.sum(),
)
}
pub fn part_two(input: &str) -> Option<isize> {
let mut map = HashMap::new();
for (a, b) in parse_input(input) {
map.entry(a).or_insert((0, 0)).0 += 1;
map.entry(b).or_insert((0, 0)).1 += 1;
}
Some(map.into_iter().map(|(k, v)| k * v.0 * v.1).sum())
}
aoc::solution!(1);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 1)), Some(11));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 1)), Some(31));
}
}

81
src/bin/02.rs Normal file
View File

@@ -0,0 +1,81 @@
use itertools::Itertools;
fn parse_input(input: &str) -> impl Iterator<Item = Vec<isize>> + '_ {
input.lines().map(|l| {
l.split(' ')
.filter_map(|x| x.parse::<isize>().ok())
.collect_vec()
})
}
fn check_report(mut r: impl Iterator<Item = isize>) -> bool {
let mut low = 0;
let mut high = 0;
let mut prev = r.next().expect("Invalid input file?");
for n in r {
let diff = n - prev;
prev = n;
if diff == 0 {
return false;
};
if !(-3..=3).contains(&diff) {
return false;
}
if diff > 0 {
high += 1;
}
if diff < 0 {
low += 1;
}
}
!(low > 0 && high > 0)
}
pub fn part_one(input: &str) -> Option<usize> {
parse_input(input)
.map(|r| check_report(r.into_iter()))
.filter(|&x| x)
.count()
.into()
}
pub fn part_two(input: &str) -> Option<usize> {
Some(
parse_input(input)
.map(|r| {
(0..r.len()).any(|i| {
check_report(
// skip nth idx
r.iter()
.enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, &v)| v),
)
})
})
.filter(|x| *x)
.count(),
)
}
aoc::solution!(2);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 2)), Some(2));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 2)), Some(4));
}
}

52
src/bin/03.rs Normal file
View File

@@ -0,0 +1,52 @@
use regex::Regex;
fn solve(input: &str, flip: bool) -> usize {
let r = Regex::new(r"mul\((\d+),(\d+)\)|do\(\)|don't\(\)").unwrap();
let mut enabled = true;
let mut res = 0;
for c in r.captures_iter(input) {
let full = c.get(0).unwrap().as_str();
match &full[..=2] {
"mul" => {
if enabled {
let a: usize = c.get(1).unwrap().as_str().parse().unwrap();
let b: usize = c.get(2).unwrap().as_str().parse().unwrap();
res += a * b;
}
}
"do(" => enabled = true,
"don" => enabled = !flip,
_ => unreachable!(),
}
}
res
}
pub fn part_one(input: &str) -> Option<usize> {
solve(input, false).into()
}
pub fn part_two(input: &str) -> Option<usize> {
solve(input, true).into()
}
aoc::solution!(3);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 3)),
Some(161)
);
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 3)), Some(48));
}
}

85
src/bin/04.rs Normal file
View File

@@ -0,0 +1,85 @@
use itertools::Itertools;
pub fn part_one(input: &str) -> Option<u32> {
let g = input.lines().map(|x| x.chars().collect_vec()).collect_vec();
#[rustfmt::skip]
let dirs = [
(1, 0), (1, 1), (0, 1), (-1, 1),
(-1, 0), (-1, -1), (0, -1), (1, -1),
];
let xmas = ['X', 'M', 'A', 'S'];
let mut count = 0;
for (i, row) in g.iter().enumerate() {
for (j, _) in row.iter().enumerate() {
for &(di, dj) in &dirs {
let found_xmas = (0..4).all(|x| {
let ni = i.wrapping_add((di * x) as usize);
let nj = j.wrapping_add((dj * x) as usize);
g.get(ni)
.and_then(|y| y.get(nj))
.copied()
.unwrap_or_default()
== xmas[x as usize]
});
if found_xmas {
count += 1;
}
}
}
}
count.into()
}
pub fn part_two(input: &str) -> Option<u32> {
let g = input.lines().map(|x| x.chars().collect_vec()).collect_vec();
let dirs = [[(-1, -1), (0, 0), (1, 1)], [(1, -1), (0, 0), (-1, 1)]];
let x_mas = [['M', 'A', 'S'], ['S', 'A', 'M']];
let mut count = 0;
for (i, row) in g.iter().enumerate() {
for (j, _) in row.iter().enumerate() {
let found_x_mas = dirs.iter().all(|d| {
let s = d.iter().map(|&(di, dj)| {
let ni = i.wrapping_add(di as usize);
let nj = j.wrapping_add(dj as usize);
g.get(ni)
.and_then(|y| y.get(nj))
.copied()
.unwrap_or_default()
});
x_mas
.iter()
.any(|x| x.iter().copied().zip(s.clone()).all(|(a, b)| a == b))
});
if found_x_mas {
count += 1
}
}
}
count.into()
}
aoc::solution!(4);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 4)), Some(18));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 4)), Some(9));
}
}

97
src/bin/05.rs Normal file
View File

@@ -0,0 +1,97 @@
use std::cmp::Ordering;
use hashbrown::HashSet;
use itertools::Itertools;
#[derive(Debug)]
struct PageUpdates {
raw: Vec<usize>,
sorted: Vec<usize>,
}
impl PageUpdates {
fn middle(&self) -> usize {
self.sorted[self.sorted.len() / 2]
}
fn is_sorted(&self) -> bool {
self.raw == self.sorted
}
}
fn parse_updates(input: &str) -> Vec<PageUpdates> {
let (page_orderings, updates) = input.split_once("\n\n").unwrap();
let mut ordering = HashSet::new();
for line in page_orderings.lines() {
let (a, b) = line.split_once('|').unwrap();
let a = a.parse().unwrap();
let b = b.parse().unwrap();
ordering.insert((a, b));
}
let cmp = |&a: &usize, &b: &usize| {
if ordering.contains(&(a, b)) {
Ordering::Less
} else if ordering.contains(&(b, a)) {
Ordering::Greater
} else {
Ordering::Equal
}
};
let mut page_updates = vec![];
for update in updates.lines() {
let raw = update
.split(',')
.filter_map(|x| x.parse().ok())
.collect_vec();
let mut sorted = raw.clone();
sorted.sort_by(cmp);
page_updates.push(PageUpdates { raw, sorted });
}
page_updates
}
pub fn part_one(input: &str) -> Option<usize> {
parse_updates(input)
.iter()
.filter(|pu| pu.is_sorted())
.map(PageUpdates::middle)
.sum::<usize>()
.into()
}
pub fn part_two(input: &str) -> Option<usize> {
parse_updates(input)
.iter()
.filter(|pu| !pu.is_sorted())
.map(PageUpdates::middle)
.sum::<usize>()
.into()
}
aoc::solution!(5);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 5)),
Some(143)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 5)),
Some(123)
);
}
}

97
src/bin/06.rs Normal file
View File

@@ -0,0 +1,97 @@
use hashbrown::HashSet;
use itertools::Itertools;
const DIRS: [(isize, isize); 4] = [(-1, 0), (0, 1), (1, 0), (0, -1)];
fn count_steps(
grid: &[Vec<char>],
start: (usize, usize),
visited: &mut HashSet<(usize, usize, usize)>,
) -> Option<usize> {
visited.clear();
let (mut i, mut j) = start;
let mut turn = 0;
loop {
if !visited.insert((i, j, turn % 4)) {
return None;
}
let (di, dj) = DIRS[turn % 4];
let (ni, nj) = (i.wrapping_add(di as usize), j.wrapping_add(dj as usize));
match grid.get(ni).and_then(|row| row.get(nj)) {
Some(g) => match g {
'.' => (i, j) = (ni, nj),
'#' => turn += 1,
_ => unreachable!(),
},
None => break,
}
}
visited.len().into()
}
fn parse_grid(input: &str) -> (Vec<Vec<char>>, (usize, usize)) {
let mut grid = input.lines().map(|x| x.chars().collect_vec()).collect_vec();
let (_, i, j) = grid
.iter()
.enumerate()
.flat_map(|(i, x)| x.iter().copied().enumerate().map(move |(j, y)| (y, i, j)))
.find(|x| x.0 == '^')
.unwrap();
grid[i][j] = '.';
(grid, (i, j))
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, start) = parse_grid(input);
let mut visited = HashSet::new();
count_steps(&grid, start, &mut visited);
HashSet::<(usize, usize)>::from_iter(visited.into_iter().map(|(x, y, _)| (x, y)))
.len()
.into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (mut grid, start) = parse_grid(input);
let mut count = 0;
let mut visited = HashSet::new();
for oi in 0..grid.len() {
for oj in 0..grid[0].len() {
if (oi, oj) == start || grid[oi][oj] == '#' {
continue;
}
grid[oi][oj] = '#';
if count_steps(&grid, start, &mut visited).is_none() {
count += 1;
}
grid[oi][oj] = '.';
}
}
count.into()
}
aoc::solution!(6);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 6)), Some(41));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 6)), Some(6));
}
}

81
src/bin/07.rs Normal file
View File

@@ -0,0 +1,81 @@
use itertools::Itertools;
fn calibrate_equation(
fns: &[fn(a: usize, b: usize) -> usize],
target: usize,
nums: &[usize],
acc: usize,
) -> bool {
if nums.is_empty() {
return target == acc;
}
if acc > target {
return false;
}
fns.iter()
.any(|f| calibrate_equation(fns, target, &nums[1..], f(acc, nums[0])))
}
fn parse_input(input: &str) -> Vec<Vec<usize>> {
input
.lines()
.map(|l| {
l.split([' ', ':'])
.filter_map(|x| x.parse().ok())
.collect_vec()
})
.collect_vec()
}
fn sum(a: usize, b: usize) -> usize {
a + b
}
fn prod(a: usize, b: usize) -> usize {
a * b
}
fn concat(a: usize, b: usize) -> usize {
a * 10_usize.pow(b.ilog10() + 1) + b
}
pub fn part_one(input: &str) -> Option<usize> {
parse_input(input)
.into_iter()
.filter(|n| calibrate_equation(&[sum, prod], n[0], &n[2..], n[1]))
.map(|n| n[0])
.sum::<usize>()
.into()
}
pub fn part_two(input: &str) -> Option<usize> {
parse_input(input)
.into_iter()
.filter(|n| calibrate_equation(&[sum, prod, concat], n[0], &n[2..], n[1]))
.map(|n| n[0])
.sum::<usize>()
.into()
}
aoc::solution!(7);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 7)),
Some(3749)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 7)),
Some(11387)
);
}
}

104
src/bin/08.rs Normal file
View File

@@ -0,0 +1,104 @@
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
#[allow(clippy::type_complexity)]
fn parse_input(input: &str) -> (Vec<Vec<char>>, HashMap<char, Vec<(isize, isize)>>) {
let grid = input.lines().map(|s| s.chars().collect_vec()).collect_vec();
let mut antennas: HashMap<char, Vec<(isize, isize)>> = HashMap::new();
for (i, row) in grid.iter().enumerate() {
for (j, cell) in row.iter().copied().enumerate().filter(|(_, c)| *c != '.') {
antennas
.entry(cell)
.or_default()
.push((i as isize, j as isize));
}
}
(grid, antennas)
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, antennas) = parse_input(input);
let mut set = HashSet::new();
for v in antennas.values() {
for (s, (i, j)) in v.iter().copied().enumerate() {
for (ii, jj) in v.iter().copied().skip(s + 1) {
let (di, dj) = (i - ii, j - jj);
let (a00, a01) = (i + di, j + dj);
let (a10, a11) = (ii - di, jj - dj);
if grid
.get(a00 as usize)
.and_then(|row| row.get(a01 as usize))
.is_some()
{
set.insert((a00, a01));
}
if grid
.get(a10 as usize)
.and_then(|row| row.get(a11 as usize))
.is_some()
{
set.insert((a10, a11));
}
}
}
}
set.len().into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (grid, antennas) = parse_input(input);
let mut set = HashSet::new();
for v in antennas.values() {
for (s, (i, j)) in v.iter().copied().enumerate() {
for (ii, jj) in v.iter().copied().skip(s + 1) {
let (di, dj) = (i - ii, j - jj);
let (mut a00, mut a01) = (i, j);
let (mut a10, mut a11) = (ii, jj);
while grid
.get(a00 as usize)
.and_then(|r| r.get(a01 as usize))
.is_some()
{
set.insert((a00, a01));
(a00, a01) = (a00 + di, a01 + dj);
}
while grid
.get(a10 as usize)
.and_then(|r| r.get(a11 as usize))
.is_some()
{
set.insert((a10, a11));
(a10, a11) = (a10 - di, a11 - dj);
}
}
}
}
set.len().into()
}
aoc::solution!(8);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 8)), Some(14));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 8)), Some(34));
}
}

148
src/bin/09.rs Normal file
View File

@@ -0,0 +1,148 @@
use std::ops::Range;
use itertools::Itertools;
fn map_disk(input: &str) -> Vec<Option<usize>> {
let disk_map = input.chars().filter_map(|x| x.to_digit(10)).collect_vec();
let mut disk = Vec::with_capacity(disk_map.len() * 10);
for (i, n) in disk_map.iter().copied().enumerate() {
let id = if i % 2 == 0 { Some(i / 2) } else { None };
for _ in 0..n {
disk.push(id);
}
}
disk
}
fn disk_checksum(disk: &[Option<usize>]) -> usize {
disk.iter()
.enumerate()
.filter(|x| x.1.is_some())
.map(|(i, x)| i * x.unwrap())
.sum()
}
pub fn part_one(input: &str) -> Option<usize> {
let mut disk = map_disk(input);
let mut left_ptr = 0;
let mut right_ptr = disk.len() - 1;
while left_ptr < right_ptr {
if disk[left_ptr].is_some() {
left_ptr += 1;
continue;
}
if disk[right_ptr].is_none() {
right_ptr -= 1;
continue;
}
disk.swap(left_ptr, right_ptr);
left_ptr += 1;
right_ptr -= 1;
}
disk_checksum(&disk).into()
}
fn next_move_candidate(disk: &[Option<usize>], mut start: usize) -> Option<Range<usize>> {
while start > 0 && disk[start].is_none() {
start -= 1;
}
if start == 0 && disk[start].is_none() {
return None;
}
let mut end = start;
while end > 0
&& disk
.get(end)
.copied()
.flatten()
.filter(|&x| Some(x) == disk[start])
.is_some()
{
end -= 1;
}
Some(end + 1..start + 1)
}
fn find_empty_space(
disk: &[Option<usize>],
size: usize,
index_limit: usize,
) -> Option<Range<usize>> {
let mut i = 0;
loop {
while i < index_limit && disk[i].is_some() {
i += 1;
}
if i == index_limit {
break;
}
let mut j = i;
while j < index_limit && disk[j].is_none() {
j += 1;
}
if j - i >= size {
return Some(i..i + size);
}
i = j
}
None
}
pub fn part_two(input: &str) -> Option<usize> {
let mut disk = map_disk(input);
let mut n = disk.len() - 1;
while n > 0 {
let c = next_move_candidate(&disk, n);
if c.is_none() {
break;
}
let c = c.unwrap();
if c.start == 0 {
break;
}
n = c.start - 1;
let t = find_empty_space(&disk, c.len(), c.start);
if t.is_none() {
continue;
}
let t = t.unwrap();
c.zip(t).map(|(cc, tt)| disk.swap(cc, tt)).count();
}
disk_checksum(&disk).into()
}
aoc::solution!(9);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 9)),
Some(1928)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 9)),
Some(2858)
);
}
}

89
src/bin/10.rs Normal file
View File

@@ -0,0 +1,89 @@
use hashbrown::HashMap;
use itertools::Itertools;
fn score_trailhead(
acc: &mut HashMap<(usize, usize), usize>,
grid: &[Vec<usize>],
start: (usize, usize),
) {
let (i, j) = start;
let n = grid[i][j];
if n == 9 {
*acc.entry((i, j)).or_default() += 1;
}
let dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)];
for (di, dj) in dirs {
let (ni, nj) = (i.wrapping_add(di as usize), j.wrapping_add(dj as usize));
if let Some(nn) = grid.get(ni).and_then(|row| row.get(nj)) {
if *nn == n + 1 {
score_trailhead(acc, grid, (ni, nj));
}
}
}
}
fn parse_grid(input: &str) -> Vec<Vec<usize>> {
input
.lines()
.map(|x| {
x.chars()
.map(|y| y.to_digit(10).unwrap_or(11) as usize)
.collect_vec()
})
.collect_vec()
}
pub fn part_one(input: &str) -> Option<usize> {
let grid = parse_grid(input);
let mut score = 0;
let mut acc = HashMap::new();
for (i, row) in grid.iter().enumerate() {
for (j, _) in row.iter().copied().enumerate().filter(|x| x.1 == 0) {
acc.clear();
score_trailhead(&mut acc, &grid, (i, j));
score += acc.len();
}
}
score.into()
}
pub fn part_two(input: &str) -> Option<usize> {
let grid = parse_grid(input);
let mut acc = HashMap::new();
for (i, row) in grid.iter().enumerate() {
for (j, _) in row.iter().copied().enumerate().filter(|x| x.1 == 0) {
score_trailhead(&mut acc, &grid, (i, j));
}
}
acc.values().sum::<usize>().into()
}
aoc::solution!(10);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 10)),
Some(36)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 10)),
Some(81)
);
}
}

72
src/bin/11.rs Normal file
View File

@@ -0,0 +1,72 @@
use std::mem::swap;
use hashbrown::HashMap;
use itertools::Itertools;
fn blink(map: &HashMap<usize, usize>, nmap: &mut HashMap<usize, usize>) {
for (&k, &v) in map.iter() {
match k {
0 => *nmap.entry(1).or_default() += v,
x if (x.ilog10() + 1) % 2 == 0 => {
let xlen = x.ilog10() + 1;
let xhalf = 10_usize.pow(xlen / 2);
*nmap.entry(x / xhalf).or_default() += v;
*nmap.entry(x % xhalf).or_default() += v;
}
x => *nmap.entry(x * 2024).or_default() += v,
}
}
}
fn solve(input: &str, blinks: usize) -> usize {
let v = input
.split(" ")
.filter_map(|x| x.parse().ok())
.collect_vec();
let mut map = HashMap::<usize, usize>::new();
for vv in v {
*map.entry(vv).or_default() += 1;
}
let mut nmap = HashMap::<usize, usize>::new();
for _ in 0..blinks {
blink(&map, &mut nmap);
map.clear();
swap(&mut map, &mut nmap);
}
map.values().sum()
}
pub fn part_one(input: &str) -> Option<usize> {
solve(input, 25).into()
}
pub fn part_two(input: &str) -> Option<usize> {
solve(input, 75).into()
}
aoc::solution!(11);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 11)),
Some(55312)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 11)),
Some(65601038650482)
);
}
}

109
src/bin/12.rs Normal file
View File

@@ -0,0 +1,109 @@
use hashbrown::HashSet;
use itertools::Itertools;
fn count_sides(perim: &mut [(isize, isize)]) -> usize {
perim.sort();
let mut sides = 0;
let mut k = 0;
while k < perim.len() {
let (mut i, mut j) = perim[k];
while let Some((ni, nj)) = perim.get(k + 1).copied() {
if i != ni || nj - j != 3 {
break;
}
(i, j) = (ni, nj);
k += 1;
}
sides += 1;
k += 1;
}
sides
}
pub fn solve(input: &str) -> (usize, usize) {
let grid = input.lines().map(|x| x.chars().collect_vec()).collect_vec();
let mut price = 0;
let mut discounted = 0;
let mut stack = Vec::new();
let mut visited = HashSet::new();
let mut ver_sides = Vec::new();
let mut hor_sides = Vec::new();
for p in (0..grid.len()).flat_map(|x| (0..grid[x].len()).map(move |y| (x, y))) {
if visited.contains(&p) {
continue;
}
let matcher = grid[p.0][p.1];
let mut area = 0;
ver_sides.clear();
hor_sides.clear();
stack.push(p);
while let Some((i, j)) = stack.pop() {
if !visited.insert((i, j)) {
continue;
}
area += 1;
for (di, dj) in [(0, 1), (1, 0), (-1, 0), (0, -1)] {
let ni = i.wrapping_add(di as usize);
let nj = j.wrapping_add(dj as usize);
let fi = i as isize * 3 + di;
let fj = j as isize * 3 + dj;
match grid.get(ni).and_then(|row| row.get(nj)) {
Some(m) if *m == matcher => stack.push((ni, nj)),
_ if dj == 0 => hor_sides.push((fi, fj)),
_ if dj != 0 => ver_sides.push((fj, fi)),
_ => unreachable!(),
}
}
}
price += area * (hor_sides.len() + ver_sides.len());
discounted += area * (count_sides(&mut hor_sides) + count_sides(&mut ver_sides));
}
(price, discounted)
}
pub fn part_one(input: &str) -> Option<usize> {
solve(input).0.into()
}
pub fn part_two(input: &str) -> Option<usize> {
solve(input).1.into()
}
aoc::solution!(12);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 12)),
Some(1930)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 12)),
Some(1206)
);
}
}

79
src/bin/13.rs Normal file
View File

@@ -0,0 +1,79 @@
use regex::Regex;
fn parse_input(input: &str) -> Vec<[(isize, isize); 3]> {
let r = Regex::new(r".*X(?:=|\+)(\d*), Y(?:=|\+)(\d*)").unwrap();
let mut parsed = Vec::new();
for machine in input.split("\n\n") {
let mut p = [(0, 0); 3];
for (idx, line) in machine.lines().enumerate() {
let c = r.captures(line).unwrap();
let x: isize = c.get(1).unwrap().as_str().parse().unwrap();
let y: isize = c.get(2).unwrap().as_str().parse().unwrap();
p[idx] = (x, y);
}
parsed.push(p)
}
parsed
}
fn solve(m: ((isize, isize), (isize, isize)), v: (isize, isize)) -> Option<isize> {
let det = m.0 .0 * m.1 .1 - m.0 .1 * m.1 .0;
if det == 0 {
return None;
}
let x = (v.0 * m.1 .1 - m.0 .1 * v.1) as f64 / det as f64;
let y = (m.0 .0 * v.1 - v.0 * m.1 .0) as f64 / det as f64;
if x.trunc() != x || y.trunc() != y {
return None;
}
Some(x as isize * 3 + y as isize)
}
pub fn part_one(input: &str) -> Option<isize> {
parse_input(input)
.into_iter()
.filter_map(|[(a1, b1), (a2, b2), t]| solve(((a1, a2), (b1, b2)), t))
.sum::<isize>()
.into()
}
pub fn part_two(input: &str) -> Option<isize> {
let prize_add = 10_000_000_000_000;
parse_input(input)
.into_iter()
.filter_map(|[(a0, b0), (a1, b1), (t0, t1)]| {
solve(((a0, a1), (b0, b1)), (t0 + prize_add, t1 + prize_add))
})
.sum::<isize>()
.into()
}
aoc::solution!(13);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 13)),
Some(480)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 13)),
Some(875318608908)
);
}
}

107
src/bin/14.rs Normal file
View File

@@ -0,0 +1,107 @@
use hashbrown::HashMap;
use num_integer::Integer;
use regex::Regex;
fn parse_input(input: &str) -> Vec<((isize, isize), (isize, isize))> {
let r = Regex::new(r"p=(\d+),(\d+) v=(-?\d+),(-?\d+)").unwrap();
let mut parsed = Vec::new();
for line in input.lines() {
let c = r.captures(line).unwrap();
let j: isize = c.get(1).unwrap().as_str().parse().unwrap();
let i: isize = c.get(2).unwrap().as_str().parse().unwrap();
let y: isize = c.get(3).unwrap().as_str().parse().unwrap();
let x: isize = c.get(4).unwrap().as_str().parse().unwrap();
parsed.push(((i, j), (x, y)));
}
parsed
}
pub fn part_one(input: &str) -> Option<usize> {
let parsed = parse_input(input);
let mut r = [0; 4];
let h = 103;
let w = 101;
for ((pi, pj), (vi, vj)) in parsed {
let ni = (pi + vi * 100).rem_euclid(h);
let nj = (pj + vj * 100).rem_euclid(w);
if ni < h / 2 && nj > w / 2 {
r[0] += 1;
}
if ni < h / 2 && nj < w / 2 {
r[1] += 1;
}
if ni > h / 2 && nj < w / 2 {
r[2] += 1;
}
if ni > h / 2 && nj > w / 2 {
r[3] += 1;
}
}
r.into_iter().product::<usize>().into()
}
pub fn part_two(input: &str) -> Option<usize> {
let pv = parse_input(input);
let h = 103;
let w = 101;
let mut n = 1;
// hardcoded side lengths of the "easter egg"
let th = 33;
let tw = 31;
for ((pi, pj), (vi, vj)) in pv.iter().copied() {
let mut m = 1;
loop {
let ni = (pi + vi * m).rem_euclid(h);
let nj = (pj + vj * m).rem_euclid(w);
if (ni, nj) == (pi, pj) {
break;
}
m += 1;
}
n = n.lcm(&m);
}
let mut sh = HashMap::new();
let mut sw = HashMap::new();
for m in 0..n {
sh.clear();
sw.clear();
for ((pi, pj), (vi, vj)) in pv.iter().copied() {
let ni = (pi + vi * m).rem_euclid(h);
let nj = (pj + vj * m).rem_euclid(w);
*sw.entry(ni).or_default() += 1_usize;
*sh.entry(nj).or_default() += 1_usize;
}
let ch = sh.values().filter(|&&x: &&usize| x >= th).count();
let cw = sw.values().filter(|&&x: &&usize| x >= tw).count();
if ch >= 2 && cw >= 2 {
return Some(n as usize);
}
}
None
}
aoc::solution!(14);

215
src/bin/15.rs Normal file
View File

@@ -0,0 +1,215 @@
use hashbrown::HashSet;
use itertools::Itertools;
struct ParsedInput {
map: Vec<Vec<char>>,
dirs: Vec<(isize, isize)>,
start: (usize, usize),
}
fn dir_mapper(c: char) -> (isize, isize) {
match c {
'>' => (0, 1),
'<' => (0, -1),
'v' => (1, 0),
'^' => (-1, 0),
_ => unreachable!("found dir {c}"),
}
}
fn parse_input(input: &str, extend: bool) -> ParsedInput {
let (umap, uins) = input.split_once("\n\n").unwrap();
let mut map = Vec::new();
for line in umap.lines() {
let mut row: Vec<char> = line.chars().collect();
if extend {
row = line
.chars()
.flat_map(|x| {
let y = match x {
'#' => "##",
'O' => "[]",
'.' => "..",
'@' => "@.",
_ => unreachable!("found {x}"),
};
y.chars()
})
.collect()
};
map.push(row);
}
let dirs = uins
.lines()
.flat_map(|x| x.chars().map(dir_mapper))
.collect_vec();
let (si, sj, _) = map
.iter()
.enumerate()
.flat_map(|(i, x)| x.iter().copied().enumerate().map(move |(j, y)| (i, j, y)))
.find(|x| x.2 == '@')
.unwrap();
ParsedInput {
map,
dirs,
start: (si, sj),
}
}
fn boxes_gps(map: &[Vec<char>]) -> usize {
map.iter()
.enumerate()
.flat_map(|(i, x)| {
x.iter()
.enumerate()
.filter(|(_, y)| ['O', '['].contains(y))
.map(move |(j, _)| 100 * i + j)
})
.sum()
}
pub fn part_one(input: &str) -> Option<usize> {
let ParsedInput {
mut map,
dirs,
start: (mut si, mut sj),
} = parse_input(input, false);
for (di, dj) in dirs.iter().copied() {
let mut box_count = 0;
loop {
if map[si][sj] == 'O' {
box_count += 1;
map[si][sj] = '.';
}
map[si][sj] = '.';
let ni = si.wrapping_add(di as usize);
let nj = sj.wrapping_add(dj as usize);
if map[ni][nj] == '#' {
break;
}
(si, sj) = (ni, nj);
if map[si][sj] == '.' {
break;
}
}
let (di, dj) = (-di, -dj);
for _ in 0..box_count {
map[si][sj] = 'O';
si = si.wrapping_add(di as usize);
sj = sj.wrapping_add(dj as usize);
}
map[si][sj] = '@';
}
boxes_gps(&map).into()
}
fn can_move_and_mark(
acc: &mut HashSet<(usize, usize, usize)>,
mark: usize,
map: &[Vec<char>],
loc: (usize, usize),
dir: (isize, isize),
) -> bool {
let (li, mut lj) = loc;
match map[li][lj] {
'.' => return true,
'#' => return false,
']' => lj -= 1,
_ => (),
}
let mut cmm = |l, dir| can_move_and_mark(acc, mark + 1, map, l, dir);
let can_move = match dir {
(0, -1) => cmm((li, lj - 1), dir),
(0, 1) => cmm((li, lj + 2), dir),
(-1, 0) => cmm((li - 1, lj), dir) && cmm((li - 1, lj + 1), dir),
(1, 0) => cmm((li + 1, lj), dir) && cmm((li + 1, lj + 1), dir),
x => unreachable!("found dir {x:?}"),
};
if can_move {
acc.insert((li, lj, mark));
}
can_move
}
pub fn part_two(input: &str) -> Option<usize> {
let ParsedInput {
mut map,
dirs,
start: (mut si, mut sj),
} = parse_input(input, true);
let mut marker = HashSet::new();
let mut sorter = Vec::new();
for (di, dj) in dirs.iter().copied() {
marker.clear();
sorter.clear();
let ni = si.wrapping_add(di as usize);
let nj = sj.wrapping_add(dj as usize);
if can_move_and_mark(&mut marker, 0, &map, (ni, nj), (di, dj)) {
sorter.extend(marker.drain());
sorter.sort_by_key(|x| -(x.2 as isize));
for (mi, mj, _) in sorter.iter().copied() {
let nmi = mi.wrapping_add(di as usize);
let nmj = mj.wrapping_add(dj as usize);
map[mi][mj] = '.';
map[mi][mj + 1] = '.';
map[nmi][nmj] = '[';
map[nmi][nmj + 1] = ']';
}
map[si][sj] = '.';
(si, sj) = (ni, nj);
map[si][sj] = '@';
}
}
boxes_gps(&map).into()
}
aoc::solution!(15);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 15)),
Some(10092)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 15)),
Some(9021)
);
}
}

119
src/bin/16.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::{cmp::Reverse, collections::BinaryHeap};
use aoc::grid_vec::{Direction, Grid, Point};
use hashbrown::{HashMap, HashSet};
fn parse_input(input: &str) -> (Grid, Point, Point) {
let mut grid: Grid = input.parse().unwrap();
let start = grid.find(b'S').unwrap();
let end = grid.find(b'E').unwrap();
grid[start] = b'.';
grid[end] = b'.';
(grid, start, end)
}
type Prev = HashMap<(Point, Direction), HashSet<(Point, Direction)>>;
type Visited = HashMap<(Point, Direction), usize>;
fn dijkstra(grid: &Grid, start: Point) -> (Visited, Prev) {
let mut prev: Prev = HashMap::new();
let mut visited: Visited = HashMap::new();
let mut bh = BinaryHeap::new();
bh.push(Reverse((0, start, Direction::E, start, Direction::E)));
while let Some(Reverse((len, loc, dir, ploc, pdir))) = bh.pop() {
let best = visited.entry((loc, dir)).or_insert(usize::MAX);
if len > *best {
continue;
}
prev.entry((loc, dir)).or_default().insert((ploc, pdir));
*best = len;
let n = loc + dir;
if grid.get(&n).filter(|&&c| c == b'.').is_some() && !visited.contains_key(&(n, dir)) {
bh.push(Reverse((len + 1, n, dir, loc, dir)));
}
let rl = dir.rotate_left();
if !visited.contains_key(&(loc, rl)) {
bh.push(Reverse((len + 1000, loc, rl, loc, dir)));
}
let rr = dir.rotate_right();
if !visited.contains_key(&(loc, rr)) {
bh.push(Reverse((len + 1000, loc, rr, loc, dir)));
}
}
(visited, prev)
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
dijkstra(&grid, start)
.0
.into_iter()
.filter(|(k, _)| k.0 == end)
.map(|(_, v)| v)
.min()
}
pub fn part_two(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
let (visited, prev) = dijkstra(&grid, start);
let mut stack = Vec::new();
stack.push(
prev.keys()
.filter(|&&k| k.0 == end)
.min_by_key(|&k| visited.get(k))
.copied()
.unwrap(),
);
let mut visited = HashSet::new();
let mut seats = HashSet::new();
while let Some((loc, dir)) = stack.pop() {
if !visited.insert((loc, dir)) {
continue;
}
if let Some(prevs) = prev.get(&(loc, dir)) {
stack.extend(prevs);
}
seats.insert(loc);
}
seats.len().into()
}
aoc::solution!(16);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 16)),
Some(11048)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 16)),
Some(64)
);
}
}

72
src/bin/17.rs Normal file
View File

@@ -0,0 +1,72 @@
use hashbrown::HashSet;
use itertools::Itertools;
fn parse_input(input: &str) -> (usize, Vec<usize>) {
let mut input_iter = input.lines();
let ua = input_iter.next().unwrap();
let uprogram = input_iter.last().unwrap();
let a = ua.strip_prefix("Register A: ").unwrap().parse().unwrap();
let program = uprogram
.strip_prefix("Program: ")
.unwrap()
.split(',')
.filter_map(|x| x.parse().ok())
.collect();
(a, program)
}
fn quick_exec(a: usize) -> usize {
// hardcoded for my input
// below operation was produced by reducing below steps
//
// B = A % 8
// B = B ^ 7
// C = A >> B
// B = B ^ 7
// A = A >> 3
// B = B ^ C
// out(B % 8)
// jmp(0)
//
// remaining mutation after returning is A >> 3
((a % 8) ^ (a >> ((a % 8) ^ 7))) % 8
}
pub fn part_one(input: &str) -> Option<String> {
let (mut a, _) = parse_input(input);
let mut v = vec![];
while a != 0 {
v.push(quick_exec(a));
a >>= 3;
}
v.iter().join(",").into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (_, program) = parse_input(input);
let mut n_candidates = HashSet::new();
let mut candidates = HashSet::new();
candidates.insert(0);
for n in program.iter().copied().rev() {
for candidate in candidates.iter() {
n_candidates.extend(
(0..8)
.map(|shift| (candidate << 3) + shift)
.filter(|&m| quick_exec(m) == n),
);
}
candidates.clear();
candidates.extend(n_candidates.drain())
}
candidates.iter().min().copied()
}
aoc::solution!(17);

93
src/bin/18.rs Normal file
View File

@@ -0,0 +1,93 @@
use std::collections::VecDeque;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use aoc::{
grid_vec::{Direction, Point},
pnt,
};
fn parse_input(input: &str) -> Vec<Point> {
input
.lines()
.map(|x| {
x.split(',')
.filter_map(|y| y.parse().ok())
.collect_tuple()
.unwrap()
})
.map(|x: (usize, usize)| Point { i: x.1, j: x.0 })
.collect_vec()
}
fn bfs(
visited: &mut HashMap<Point, usize>,
queue: &mut VecDeque<(Point, usize)>,
corruption: &HashSet<Point>,
end: Point,
) -> Option<usize> {
visited.clear();
queue.clear();
queue.push_back((pnt!(0, 0), 0));
while let Some((p, l)) = queue.pop_front() {
if visited.contains_key(&p) {
continue;
}
visited.insert(p, l);
if p == end {
break;
}
for d in Direction::CROSS {
let n = p + d;
let in_bounds = n.bounded_by(&end);
let not_visited = !visited.contains_key(&n);
let not_corrupted = !corruption.contains(&n);
if in_bounds && not_visited && not_corrupted {
queue.push_back((n, l + 1));
}
}
}
visited.get(&end).copied()
}
pub fn part_one(input: &str) -> Option<usize> {
let falling_bytes = parse_input(input);
let mut visited = HashMap::new();
let mut queue = VecDeque::new();
let end = pnt!(70, 70);
let corruption = HashSet::from_iter(falling_bytes.iter().copied().take(1024));
bfs(&mut visited, &mut queue, &corruption, end)
}
pub fn part_two(input: &str) -> Option<String> {
let falling_bytes = parse_input(input);
let mut visited = HashMap::new();
let mut queue = VecDeque::new();
let end = pnt!(70, 70);
let mut corruption = HashSet::new();
let mut c = 0;
while bfs(&mut visited, &mut queue, &corruption, end).is_some() {
corruption.insert(falling_bytes[c]);
c += 1;
}
let fb = falling_bytes[c - 1];
Some(format!("{},{}", fb.j, fb.i))
}
aoc::solution!(18);

74
src/bin/19.rs Normal file
View File

@@ -0,0 +1,74 @@
use hashbrown::{HashMap, HashSet};
fn parse_input(input: &str) -> (HashSet<&str>, HashSet<&str>) {
let (upatterns, udesigns) = input.split_once("\n\n").unwrap();
let patterns = upatterns.split(", ").collect();
let designs = udesigns.split('\n').collect();
(patterns, designs)
}
fn count_pattern_designs<'a>(
memo: &mut HashMap<&'a str, usize>,
patterns: &HashSet<&str>,
pattern: &'a str,
) -> usize {
if pattern.is_empty() {
return 1;
}
if !memo.contains_key(pattern) {
let mut count = 0;
for p in patterns.iter() {
if let Some(stripped) = pattern.strip_prefix(p) {
count += count_pattern_designs(memo, patterns, stripped)
}
}
memo.insert(pattern, count);
}
memo[pattern]
}
pub fn part_one(input: &str) -> Option<usize> {
let (patterns, designs) = parse_input(input);
let mut memo = HashMap::new();
for d in designs.iter().copied() {
count_pattern_designs(&mut memo, &patterns, d);
}
designs.into_iter().filter(|x| memo[x] > 0).count().into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (patterns, designs) = parse_input(input);
let mut memo = HashMap::new();
for d in designs.iter().copied() {
count_pattern_designs(&mut memo, &patterns, d);
}
designs.into_iter().map(|x| memo[x]).sum::<usize>().into()
}
aoc::solution!(19);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 19)), Some(6));
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 19)),
Some(16)
);
}
}

135
src/bin/20.rs Normal file
View File

@@ -0,0 +1,135 @@
use std::collections::VecDeque;
use aoc::grid_vec::{Direction, Grid, Point};
use hashbrown::HashMap;
fn parse_input(input: &str) -> (Grid, Point, Point) {
let mut grid: Grid = input.parse().unwrap();
let start = grid.find(b'S').unwrap();
let end = grid.find(b'E').unwrap();
grid[start] = b'.';
grid[end] = b'.';
(grid, start, end)
}
fn bfs(
grid: &Grid,
start: Point,
limit: Option<usize>,
cheat: bool,
visited: &mut HashMap<Point, usize>,
queue: &mut VecDeque<(Point, usize)>,
) {
visited.clear();
queue.clear();
queue.push_back((start, 0));
while let Some((p, l)) = queue.pop_front() {
if visited.contains_key(&p) {
continue;
}
visited.insert(p, l);
for d in Direction::CROSS {
let n = p + d;
let nv = grid.get(&n);
let uwnv = *nv.unwrap_or(&b'#');
let valid_neigh = (cheat && nv.is_some()) || uwnv == b'.';
let valid_length = l < limit.unwrap_or(usize::MAX);
let unvisited = !visited.contains_key(&n);
if valid_neigh && valid_length && unvisited {
queue.push_back((n, l + 1));
}
}
}
}
fn get_cheat_neighbours_and_benchmark(
grid: &Grid,
start: Point,
cheat_limit: usize,
) -> HashMap<Point, Vec<(Point, usize)>> {
let mut map = HashMap::<Point, Vec<(Point, usize)>>::new();
let mut reuse_queue = VecDeque::new();
let mut reuse_visited = HashMap::new();
let mut valid_points = HashMap::new();
bfs(
grid,
start,
None,
false,
&mut valid_points,
&mut reuse_queue,
);
for (&s, _) in valid_points.iter() {
bfs(
grid,
s,
Some(cheat_limit),
true,
&mut reuse_visited,
&mut reuse_queue,
);
for (&point, &length) in reuse_visited.iter().filter(|(&p, _)| grid[p] == b'.') {
map.entry(s).or_default().push((point, length));
}
}
map
}
fn cheated_bfs(
grid: &Grid,
start: Point,
end: Point,
cheat_limit: usize,
increase_limit: usize,
) -> usize {
let neighbours = get_cheat_neighbours_and_benchmark(grid, start, cheat_limit);
let mut reuse_queue = VecDeque::new();
let mut from_start = HashMap::new();
let mut from_end = HashMap::new();
bfs(grid, start, None, false, &mut from_start, &mut reuse_queue);
bfs(grid, end, None, false, &mut from_end, &mut reuse_queue);
let benchmark = from_start[&end];
let mut count = 0;
for node in from_start.keys() {
for (neigh, cheat_dist) in neighbours[node].iter() {
let start_dist = from_start[node];
let end_dist = from_end[neigh];
let dist = start_dist + cheat_dist + end_dist;
if dist < benchmark && dist.abs_diff(benchmark) >= increase_limit {
count += 1;
}
}
}
count
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
cheated_bfs(&grid, start, end, 2, 100).into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
cheated_bfs(&grid, start, end, 20, 100).into()
}
aoc::solution!(20);

162
src/bin/21.rs Normal file
View File

@@ -0,0 +1,162 @@
use std::collections::VecDeque;
use hashbrown::HashMap;
use itertools::Itertools;
use cached::proc_macro::cached;
use cached::SizedCache;
#[rustfmt::skip]
#[allow(clippy::type_complexity)]
const NUMPAD: [(u8, [Option<(u8, u8)>; 4]); 11] = [
(b'0', [Some((b'2', b'^')), Some((b'A', b'>')), None, None]),
(b'1', [Some((b'2', b'>')), Some((b'4', b'^')), None, None]),
(b'2', [Some((b'0', b'v')), Some((b'1', b'<')), Some((b'3', b'>')), Some((b'5', b'^')),]),
(b'3', [Some((b'2', b'<')), Some((b'6', b'^')), Some((b'A', b'v')), None,]),
(b'4', [Some((b'1', b'v')), Some((b'5', b'>')), Some((b'7', b'^')), None,]),
(b'5', [Some((b'2', b'v')), Some((b'4', b'<')), Some((b'6', b'>')), Some((b'8', b'^')),]),
(b'6', [Some((b'3', b'v')), Some((b'5', b'<')), Some((b'9', b'^')), None,]),
(b'7', [Some((b'4', b'v')), Some((b'8', b'>')), None, None]),
(b'8', [Some((b'5', b'v')), Some((b'7', b'<')), Some((b'9', b'>')), None,]),
(b'9', [Some((b'6', b'v')), Some((b'8', b'<')), None, None]),
(b'A', [Some((b'0', b'<')), Some((b'3', b'^')), None, None]),
];
#[rustfmt::skip]
#[allow(clippy::type_complexity)]
const KEYPAD: [(u8, [Option<(u8, u8)>; 3]); 5] = [
(b'^', [Some((b'A', b'>')), Some((b'v', b'v')), None]),
(b'<', [Some((b'v', b'>')), None, None]),
(b'v', [Some((b'<', b'<')), Some((b'^', b'^')), Some((b'>', b'>'))]),
(b'>', [Some((b'v', b'<')), Some((b'A', b'^')), None]),
(b'A', [Some((b'^', b'<')), Some((b'>', b'v')), None]),
];
#[cached(
ty = "SizedCache<String, Vec<VecDeque<u8>>>",
create = "{ SizedCache::with_size(150) }",
convert = r#"{ format!("{}{s}{e}", graph.len()) }"#
)]
fn bfs(graph: &HashMap<u8, Vec<(u8, u8)>>, s: u8, e: u8) -> Vec<VecDeque<u8>> {
let mut res = Vec::new();
let mut queue = VecDeque::new();
let mut shortest = usize::MAX;
queue.push_back((s, VecDeque::new()));
while let Some((n, mut p)) = queue.pop_front() {
if n == e {
if shortest == usize::MAX {
shortest = p.len();
}
if p.len() == shortest {
p.push_back(b'A');
res.push(p);
}
continue;
}
if p.len() >= shortest {
continue;
}
for (nn, c) in graph[&n].iter().copied() {
let mut np = p.clone();
np.push_back(c);
queue.push_back((nn, np));
}
}
res
}
#[cached(
ty = "SizedCache<String, usize>",
create = "{ SizedCache::with_size(100000) }",
convert = r#"{ format!("{sequence:?}{level}{graph}") }"#
)]
fn dfs(
graphs: &[HashMap<u8, Vec<(u8, u8)>>],
graph: usize,
mut sequence: VecDeque<u8>,
level: usize,
) -> usize {
let graph = &graphs[graph];
sequence.push_front(b'A');
let mut res = 0;
for (n1, n2) in sequence.into_iter().tuple_windows() {
let paths = bfs(graph, n1, n2);
if level == 0 {
res += paths.into_iter().map(|p| p.len()).min().unwrap();
} else {
res += paths
.into_iter()
.map(|p| dfs(graphs, 1, p, level - 1))
.min()
.unwrap();
}
}
res
}
fn build_graphs() -> [HashMap<u8, Vec<(u8, u8)>>; 2] {
let mut numpad = HashMap::new();
for (k, v) in NUMPAD.iter() {
numpad.insert(*k, v.iter().filter_map(|&x| x).collect_vec());
}
let mut keypad = HashMap::new();
for (k, v) in KEYPAD.iter() {
keypad.insert(*k, v.iter().filter_map(|&x| x).collect_vec());
}
[numpad, keypad]
}
fn solve_chain(input: &str, level: usize) -> usize {
let graphs = build_graphs();
let mut res = 0;
for line in input.lines() {
let n: usize = line.strip_suffix("A").unwrap().parse().unwrap();
let sequence = VecDeque::from_iter(line.as_bytes().iter().copied());
let min_len = dfs(&graphs, 0, sequence, level);
res += min_len * n;
}
res
}
pub fn part_one(input: &str) -> Option<usize> {
Some(solve_chain(input, 2))
}
pub fn part_two(input: &str) -> Option<usize> {
Some(solve_chain(input, 25))
}
aoc::solution!(21);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 21)),
Some(126384)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 21)),
Some(154115708116294)
);
}
}

91
src/bin/22.rs Normal file
View File

@@ -0,0 +1,91 @@
use aoc::parsers::to_vec_map;
use hashbrown::{HashMap, HashSet};
fn pseudo_next(mut n: usize) -> usize {
n = ((n << 6) ^ n) % 16777216;
n = ((n >> 5) ^ n) % 16777216;
n = ((n << 11) ^ n) % 16777216;
n
}
struct MonkeyTrader {
numbers: [usize; 2001],
sequences: HashMap<[isize; 4], usize>,
}
impl MonkeyTrader {
fn new(seed: usize) -> Self {
let mut numbers = [0; 2001];
numbers[0] = seed;
let mut n = seed;
for num in numbers.iter_mut().skip(1) {
n = pseudo_next(n);
*num = n;
}
let mut sequences = HashMap::new();
for i in 4..2001 {
let mut key = [0; 4];
for j in 1..=4 {
let d1 = numbers[i - 4 + j - 1] % 10;
let d2 = numbers[i - 4 + j] % 10;
let diff = d2 as isize - d1 as isize;
key[j - 1] = diff;
}
if sequences.contains_key(&key) {
continue;
}
*sequences.entry(key).or_default() = numbers[i] % 10;
}
MonkeyTrader { numbers, sequences }
}
}
pub fn part_one(input: &str) -> Option<usize> {
to_vec_map(input, '\n', MonkeyTrader::new)
.into_iter()
.map(|x| x.numbers[2000])
.sum::<usize>()
.into()
}
pub fn part_two(input: &str) -> Option<usize> {
let traders = to_vec_map(input, '\n', MonkeyTrader::new);
let key_union: HashSet<[isize; 4]> = traders
.iter()
.flat_map(|x| x.sequences.keys().cloned())
.collect();
key_union
.into_iter()
.map(|k| {
traders
.iter()
.filter_map(|t| t.sequences.get(&k))
.sum::<usize>()
})
.max()
}
aoc::solution!(22);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 22)),
Some(37990510)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 22)),
Some(23)
);
}
}

88
src/bin/23.rs Normal file
View File

@@ -0,0 +1,88 @@
use std::collections::BTreeSet;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
fn parse_graph(input: &str) -> HashMap<&str, BTreeSet<&str>> {
let nodes: HashSet<_> = input.lines().filter_map(|x| x.split_once('-')).collect();
let mut graph: HashMap<_, BTreeSet<_>> = HashMap::new();
for (n1, n2) in nodes.iter().copied() {
graph.entry(n1).or_default().insert(n2);
graph.entry(n2).or_default().insert(n1);
}
graph
}
pub fn part_one(input: &str) -> Option<usize> {
let graph = parse_graph(input);
let mut triples = HashSet::new();
for (n1, neigh) in graph.iter() {
for n2 in neigh.iter().copied() {
for n3 in graph[n2].iter() {
if graph[n3].contains(n1) && n1.starts_with('t') {
let mut triple = [n1, n2, n3];
triple.sort();
triples.insert(triple);
}
}
}
}
triples.len().into()
}
pub fn part_two(input: &str) -> Option<String> {
let graph = parse_graph(input);
let p: BTreeSet<_> = graph.keys().copied().collect();
let r = HashSet::new();
let x = BTreeSet::new();
let mut maximals = HashSet::new();
let mut stack = Vec::new();
stack.push((r, p, x));
while let Some((r, mut p, mut x)) = stack.pop() {
if p.is_empty() && x.is_empty() {
let mut password = r.iter().copied().collect_vec();
password.sort();
maximals.insert(password.join(","));
}
while let Some(pp) = p.pop_last() {
let mut nr = r.clone();
nr.insert(pp);
let np = &p & &graph[pp];
let nx = &x & &graph[pp];
stack.push((nr, np, nx));
x.insert(pp);
}
}
maximals.into_iter().max_by_key(|x| x.len())
}
aoc::solution!(23);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 23)), Some(7));
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 23)),
Some("co,de,ka,ta".to_string())
);
}
}

132
src/bin/24.rs Normal file
View File

@@ -0,0 +1,132 @@
use std::collections::VecDeque;
use hashbrown::HashMap;
use itertools::Itertools;
use regex::Regex;
#[derive(Debug, Clone, Copy)]
enum Gate {
And,
Or,
XOr,
}
#[derive(Debug)]
struct LogicGate<'a> {
input_one: &'a str,
input_two: &'a str,
output: &'a str,
gate: Gate,
}
fn parse_input(input: &str) -> (HashMap<&str, bool>, Vec<LogicGate>) {
let (uvals, uevals) = input.split_once("\n\n").unwrap();
let vals: HashMap<_, _> = uvals
.lines()
.map(|x| x.split_once(": ").unwrap())
.map(|(k, v)| (k, v == "1"))
.collect();
let mut logic_gates = Vec::new();
let re = Regex::new(r#"((?:\w|\d){3}) (AND|XOR|OR) ((?:\w|\d){3}) -> ((?:\w|\d){3})"#).unwrap();
for capt in re.captures_iter(uevals) {
let input_one = capt.get(1).unwrap().as_str();
let gate = match capt.get(2).unwrap().as_str() {
"AND" => Gate::And,
"OR" => Gate::Or,
"XOR" => Gate::XOr,
_ => unreachable!(),
};
let input_two = capt.get(3).unwrap().as_str();
let output = capt.get(4).unwrap().as_str();
logic_gates.push(LogicGate {
input_one,
input_two,
output,
gate,
})
}
(vals, logic_gates)
}
pub fn part_one(input: &str) -> Option<usize> {
let (mut vals, logic_gates) = parse_input(input);
let mut queue = VecDeque::from(logic_gates);
while let Some(s) = queue.pop_front() {
if !vals.contains_key(s.input_one) || !vals.contains_key(s.input_two) {
queue.push_back(s);
continue;
}
let v1 = vals[s.input_one];
let v2 = vals[s.input_two];
let o = match s.gate {
Gate::And => v1 & v2,
Gate::Or => v1 | v2,
Gate::XOr => v1 ^ v2,
};
vals.insert(s.output, o);
}
let mut zs = vals
.into_iter()
.filter(|x| x.0.starts_with('z'))
.collect_vec();
zs.sort();
zs.into_iter()
.enumerate()
.filter(|x| x.1 .1)
.map(|(i, _)| 1 << i)
.sum::<usize>()
.into()
}
pub fn part_two(_input: &str) -> Option<String> {
// Used graphviz for this part
// ```rs
// let (_, logic_gates) = parse_input(input);
// println!("digraph A {{");
// for (idx, lg) in logic_gates.iter().enumerate() {
// let LogicGate {
// input_one: i1,
// input_two: i2,
// output: o1,
// gate: g,
// } = lg;
// println!("{i1} -> {g:?}_{idx}");
// println!("{i2} -> {g:?}_{idx}");
// println!("{g:?}_{idx} -> {o1}");
// }
// println!("}}");
// ```
//
// and then pipe to dot
// ```bash
// ... | dot -Tsvg -Kneato > grpah.svg
// ```
//
// i looked for errors in the pattern and found below
// solution for my input
let mut res = ["fkp", "z06", "z11", "ngr", "z31", "mfm", "bpt", "krj"];
res.sort();
res.join(",").to_string().into()
}
aoc::solution!(24);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 24)),
Some(2024)
);
}
}

57
src/bin/25.rs Normal file
View File

@@ -0,0 +1,57 @@
pub fn part_one(input: &str) -> Option<usize> {
let mut keys = Vec::new();
let mut locks = Vec::new();
for key_or_lock in input.split("\n\n") {
let lock = key_or_lock
.lines()
.next()
.unwrap()
.chars()
.all(|x| x == '#');
let mut columns = [0; 5];
for (idx, c) in key_or_lock.lines().flat_map(|x| x.chars().enumerate()) {
if c == '#' {
columns[idx] += 1;
}
}
// remove top or bottom row from count
for c in columns.iter_mut() {
*c -= 1;
}
if lock {
locks.push(columns);
} else {
keys.push(columns);
}
}
let mut count = 0;
for k in keys.iter() {
for l in locks.iter() {
if k.iter().zip(l.iter()).all(|(k, l)| k + l <= 5) {
count += 1;
}
}
}
count.into()
}
pub fn part_two(_input: &str) -> Option<String> {
"Happy christmas!".to_string().into()
}
aoc::solution!(25);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 25)), Some(3));
}
}

67
src/grid_vec/direction.rs Normal file
View File

@@ -0,0 +1,67 @@
use std::{fmt::Display, ops::Mul};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct Direction {
pub i: isize,
pub j: isize,
}
impl Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.i, self.j)
}
}
impl Mul<usize> for Direction {
type Output = Self;
fn mul(self, rhs: usize) -> Self::Output {
Self {
i: self.i * rhs as isize,
j: self.j * rhs as isize,
}
}
}
impl Direction {
pub const N: Self = Direction { i: -1, j: 0 };
pub const E: Self = Direction { i: 0, j: 1 };
pub const S: Self = Direction { i: 1, j: 0 };
pub const W: Self = Direction { i: 0, j: -1 };
pub const NE: Self = Direction { i: -1, j: 1 };
pub const NW: Self = Direction { i: -1, j: -1 };
pub const SE: Self = Direction { i: 1, j: 1 };
pub const SW: Self = Direction { i: 1, j: -1 };
pub const CROSS: [Self; 4] = [Self::N, Self::E, Self::S, Self::W];
#[rustfmt::skip]
pub const OMNI: [Self; 8] = [
Self::N, Self::E, Self::S, Self::W,
Self::NE, Self::NW, Self::SE, Self::SW
];
pub fn new(i: isize, j: isize) -> Self {
Self { i, j }
}
pub fn rotate_left(self) -> Self {
Self {
i: -self.j,
j: self.i,
}
}
pub fn rotate_right(self) -> Self {
Self {
i: self.j,
j: -self.i,
}
}
}
#[macro_export]
macro_rules! dir {
($i:literal, $j:literal) => {
Direction { i: $i, j: $j }
};
}

96
src/grid_vec/grid.rs Normal file
View File

@@ -0,0 +1,96 @@
use std::{
fmt::Display,
ops::{Index, IndexMut},
str::FromStr,
};
use super::Point;
#[derive(Debug, Clone)]
pub struct Grid {
grid: Vec<u8>,
width: usize,
}
impl FromStr for Grid {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut grid = Vec::with_capacity(s.len());
let mut width = None;
for line in s.lines() {
let w = line.len();
if width.is_none() {
width = Some(w);
}
if width.filter(|&x| x == w).is_none() {
panic!("all lines have to be of same length");
}
grid.extend(line.bytes());
}
Ok(Self {
grid,
width: width.unwrap(),
})
}
}
impl Display for Grid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, cell) in self.grid.iter().enumerate() {
write!(f, "{}", *cell as char)?;
if idx > 0 && (idx + 1) % self.width == 0 {
writeln!(f)?;
}
}
Ok(())
}
}
impl Index<Point> for Grid {
type Output = u8;
fn index(&self, index: Point) -> &Self::Output {
&self.grid[index.i * self.width + index.j]
}
}
impl IndexMut<Point> for Grid {
fn index_mut(&mut self, index: Point) -> &mut Self::Output {
&mut self.grid[index.i * self.width + index.j]
}
}
impl Grid {
pub fn find(&self, f: u8) -> Option<Point> {
let n = self
.grid
.iter()
.enumerate()
.find(|&(_, &x)| x == f)
.map(|x| x.0)?;
Some(Point::new(n / self.width, n % self.width))
}
pub fn get(&self, p: &Point) -> Option<&u8> {
if p.i >= self.grid.len() / self.width || p.j >= self.width {
return None;
}
self.grid.get(p.i * self.width + p.j)
}
pub fn get_mut(&mut self, p: &Point) -> Option<&mut u8> {
if p.i >= self.grid.len() / self.width || p.j >= self.width {
return None;
}
self.grid.get_mut(p.i * self.width + p.j)
}
pub fn size(&self) -> (usize, usize) {
(self.grid.len() / self.width, self.width)
}
}

7
src/grid_vec/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
mod direction;
mod grid;
mod point;
pub use direction::Direction;
pub use grid::Grid;
pub use point::Point;

95
src/grid_vec/point.rs Normal file
View File

@@ -0,0 +1,95 @@
use super::Direction;
use std::{
fmt::Display,
ops::{Add, AddAssign},
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct Point {
pub i: usize,
pub j: usize,
}
impl Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.i, self.j)
}
}
impl Add<Direction> for Point {
type Output = Self;
fn add(self, rhs: Direction) -> Self::Output {
Self {
i: self.i.wrapping_add(rhs.i as usize),
j: self.j.wrapping_add(rhs.j as usize),
}
}
}
impl AddAssign<Direction> for Point {
fn add_assign(&mut self, rhs: Direction) {
*self = *self + rhs
}
}
impl Point {
pub fn new(i: usize, j: usize) -> Self {
Self { i, j }
}
pub fn n(self) -> Self {
Self {
i: self.i.wrapping_sub(1),
j: self.j,
}
}
pub fn s(self) -> Self {
Self {
i: self.i.wrapping_add(1),
j: self.j,
}
}
pub fn e(self) -> Self {
Self {
i: self.i,
j: self.j.wrapping_sub(1),
}
}
pub fn w(self) -> Self {
Self {
i: self.i,
j: self.j.wrapping_add(1),
}
}
pub fn ne(self) -> Self {
self.n().e()
}
pub fn nw(self) -> Self {
self.n().w()
}
pub fn se(self) -> Self {
self.s().e()
}
pub fn sw(self) -> Self {
self.s().w()
}
pub fn bounded_by(&self, other: &Self) -> bool {
self.i <= other.i && self.j <= other.j
}
}
#[macro_export]
macro_rules! pnt {
($i:expr, $j:expr) => {
Point { i: $i, j: $j }
};
}

View File

@@ -1 +1,5 @@
#![feature(pattern)]
pub mod grid_vec;
pub mod parsers;
pub mod template;

20
src/parsers.rs Normal file
View File

@@ -0,0 +1,20 @@
use std::str::{pattern::Pattern, FromStr};
pub fn to_vec<T, P>(s: &str, pat: P) -> Vec<T>
where
T: FromStr,
P: Pattern,
{
s.split(pat).filter_map(|x| x.parse().ok()).collect()
}
pub fn to_vec_map<T, U, P>(s: &str, pat: P, func: impl FnMut(T) -> U) -> Vec<U>
where
T: FromStr,
P: Pattern,
{
s.split(pat)
.filter_map(|x| x.parse().ok())
.map(func)
.collect()
}

View File

@@ -8,11 +8,11 @@ use std::{
process,
};
const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<u32> {
const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<usize> {
None
}
pub fn part_two(input: &str) -> Option<u32> {
pub fn part_two(input: &str) -> Option<usize> {
None
}