Compare commits

...

16 Commits

Author SHA1 Message Date
0000043002 solution: day 25 2023-12-25 08:58:42 +01:00
00000420f1 solution: day 24 2023-12-25 08:57:35 +01:00
000004106a solution: day 23 2023-12-23 12:39:28 +01:00
000004006d solution: day 22 2023-12-22 18:56:44 +01:00
0000039019 solution: day 21 2023-12-21 20:26:26 +01:00
0000038000 solution: day 20 clean 2023-12-20 22:53:54 +01:00
000003704a solution: day 20 2023-12-20 14:50:01 +01:00
00000360db solution: day 19 clean 2023-12-19 18:12:27 +01:00
00000350d1 solution: day 19 2023-12-19 17:56:15 +01:00
0000034077 solution: day 18 2023-12-18 21:02:50 +01:00
0000033086 solution: day 17 2023-12-17 14:41:03 +01:00
0000032032 solution: day 16 2023-12-17 14:12:57 +01:00
00000310ba chore: fix clippy suggestions for day 14 2023-12-17 14:11:49 +01:00
0000030011 solution: day 15 2023-12-15 12:28:25 +01:00
0000029007 solution: day 14 2023-12-15 12:20:27 +01:00
00000280ae solution: day 13 clean 2023-12-13 23:15:21 +01:00
28 changed files with 2198 additions and 30 deletions

197
Cargo.lock generated
View File

@@ -17,9 +17,21 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "aoc"
version = "46.0.0"
dependencies = [
"z3",
]
[[package]]
name = "autocfg"
@@ -48,12 +60,38 @@ version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "bindgen"
version = "0.66.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
dependencies = [
"bitflags 2.4.1",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.42",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bumpalo"
version = "3.11.1"
@@ -72,12 +110,41 @@ version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
dependencies = [
"cc",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -211,6 +278,12 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.15"
@@ -372,12 +445,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.17"
@@ -399,6 +488,12 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -437,6 +532,16 @@ dependencies = [
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@@ -468,7 +573,7 @@ version = "0.10.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if",
"foreign-types",
"libc",
@@ -485,7 +590,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -507,6 +612,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -561,9 +672,38 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@@ -617,6 +757,12 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "ryu"
version = "1.0.12"
@@ -648,7 +794,7 @@ version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -694,6 +840,12 @@ dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "slab"
version = "0.4.7"
@@ -734,13 +886,24 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
@@ -931,7 +1094,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-shared",
]
@@ -965,7 +1128,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1126,3 +1289,23 @@ dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "z3"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a7ff5718c079e7b813378d67a5bed32ccc2086f151d6185074a7e24f4a565e8"
dependencies = [
"log",
"z3-sys",
]
[[package]]
name = "z3-sys"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cf70fdbc0de3f42b404f49b0d4686a82562254ea29ff0a155eef2f5430f4b0"
dependencies = [
"bindgen",
"cmake",
]

View File

@@ -21,4 +21,6 @@ authors.workspace = true
repository.workspace = true
[dependencies]
# so much for no dependencies this year, but I really cba so here it is
z3 = { version = "0.12.1", features = [ "static-link-z3" ] }

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,14 @@
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)

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

@@ -0,0 +1,17 @@
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}
{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}

5
data/examples/20-1.txt Normal file
View File

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

5
data/examples/20-2.txt Normal file
View File

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

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

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

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

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

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

@@ -0,0 +1,23 @@
#.#####################
#.......#########...###
#######.#########.#.###
###.....#.>.>.###.#.###
###v#####.#v#.###.#.###
###.>...#.#.#.....#...#
###v###.#.#.#########.#
###...#.#.#.......#...#
#####.#.#.#######.#.###
#.....#.#.#.......#...#
#.#####.#.#.#########v#
#.#...#...#...###...>.#
#.#.#v#######v###.###v#
#...#.>.#...>.>.#.###.#
#####v#.#.###v#.#.###.#
#.....#...#...#.#.#...#
#.#########.###.#.#.###
#...###...#...#...#.###
###.###.#.###v#####v###
#...#...#.#.>.>.#.>.###
#.###.###.#.###.#.#v###
#.....###...###...#...#
#####################.#

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

@@ -0,0 +1,5 @@
19, 13, 30 @ -2, 1, -2
18, 19, 22 @ -1, -1, -2
20, 25, 34 @ -2, -2, -4
12, 31, 28 @ -1, -2, -1
20, 19, 15 @ 1, -5, -3

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

@@ -0,0 +1,13 @@
jqt: rhn xhk nvd
rsh: frs pzl lsr
xhk: hfx
cmg: qnr nvd lhk bvb
rhn: xhk bvb hfx
bvb: xhk hfx
pzl: lsr hfx nvd
qnr: nvd
ntq: jqt hfx bvb xhk
nvd: lhk
lsr: lhk
rzs: qnr cmg lsr rsh
frs: qnr lhk lsr

View File

@@ -1,6 +1,5 @@
fn mirror_h(shape: &Vec<Vec<char>>, smudges: usize) -> Option<usize> {
(1..shape.len())
.filter(|&i| {
fn mirror_h(shape: &[Vec<char>], smudges: usize) -> Option<usize> {
(1..shape.len()).find(|&i| {
shape
.iter()
.skip(i)
@@ -14,10 +13,10 @@ fn mirror_h(shape: &Vec<Vec<char>>, smudges: usize) -> Option<usize> {
.sum::<usize>()
== smudges
})
.max()
}
fn mirror_v(shape: &Vec<Vec<char>>, smudges: usize) -> Option<usize> {
let shape = (0..shape[0].len())
fn mirror_v(shape: &[Vec<char>], smudges: usize) -> Option<usize> {
let shape: Vec<Vec<char>> = (0..shape[0].len())
.map(|col| (0..shape.len()).map(|row| shape[row][col]).collect())
.collect();
@@ -28,10 +27,9 @@ fn solve(input: &str, smudges: usize) -> usize {
input
.split("\n\n")
.map(|x| x.lines().map(|line| line.chars().collect()).collect())
.map(|shape| {
let v = mirror_v(&shape, smudges).unwrap_or_default();
let h = mirror_h(&shape, smudges).unwrap_or_default();
v + h * 100
.map(|shape: Vec<Vec<char>>| {
mirror_v(&shape, smudges).unwrap_or_default()
+ mirror_h(&shape, smudges).unwrap_or_default() * 100
})
.sum()
}

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

@@ -0,0 +1,115 @@
use std::{collections::HashMap, iter::once};
enum Tilt {
North,
West,
South,
East,
}
fn get_load(floor: &[Vec<char>]) -> usize {
floor
.iter()
.enumerate()
.flat_map(|(i, x)| x.iter().map(move |y| (i, y)))
.filter(|x| *x.1 == 'O')
.map(|x| floor.len() - x.0)
.sum()
}
fn swap<T: Copy>(floor: &mut [Vec<T>], from: (usize, usize), to: (usize, usize)) {
let a = floor[from.0][from.1];
let b = floor[to.0][to.1];
floor[from.0][from.1] = b;
floor[to.0][to.1] = a;
}
fn tilt(floor: &mut [Vec<char>], tilt: Tilt) {
let (inner, outer) = match tilt {
Tilt::North | Tilt::South => (floor[0].len(), floor.len()),
Tilt::West | Tilt::East => (floor.len(), floor[0].len()),
};
let inx_n = |(i, j)| (j, i);
let inx_s = |(i, j)| (inner - 1 - j, i);
let inx_w = |(i, j)| (i, j);
let inx_e = |(i, j)| (i, inner - 1 - j);
for i in 0..outer {
let mut ptr = 0;
for j in 0..inner {
let ((ii, jj), (pi, pj)) = match tilt {
Tilt::North => (inx_n((i, j)), inx_n((i, ptr))),
Tilt::South => (inx_s((i, j)), inx_s((i, ptr))),
Tilt::East => (inx_e((i, j)), inx_e((i, ptr))),
Tilt::West => (inx_w((i, j)), inx_w((i, ptr))),
};
match floor[ii][jj] {
'O' => {
swap(floor, (ii, jj), (pi, pj));
ptr += 1;
}
'#' => ptr = j + 1,
_ => (),
}
}
}
}
fn tilt_cycle(floor: &mut [Vec<char>]) {
use Tilt::*;
tilt(floor, North);
tilt(floor, West);
tilt(floor, South);
tilt(floor, East);
}
pub fn part_one(input: &str) -> Option<usize> {
let mut f: Vec<Vec<_>> = input.lines().map(|x| x.chars().collect()).collect();
tilt(&mut f, Tilt::North);
Some(get_load(&f))
}
pub fn part_two(input: &str) -> Option<usize> {
let mut f: Vec<Vec<_>> = input.lines().map(|x| x.chars().collect()).collect();
let mut memo: HashMap<String, usize> = HashMap::new();
for i in 1.. {
tilt_cycle(&mut f);
let repr = f
.iter()
.flat_map(|x| x.iter().chain(once(&'\n')))
.collect::<String>();
if let Some(ii) = memo.insert(repr, i) {
let m = i - ii;
let shift = (1_000_000_000 - ii) % m;
for _ in 0..shift {
tilt_cycle(&mut f);
}
break;
}
}
Some(get_load(&f))
}
aoc::solution!(14);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 14)),
Some(136)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 14)),
Some(64)
);
}
}

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

@@ -0,0 +1,68 @@
fn hash(s: &str) -> usize {
let mut total = 0;
for c in s.chars().map(|x| x as usize) {
total += c;
total *= 17;
total %= 256;
}
total
}
pub fn part_one(input: &str) -> Option<usize> {
Some(input.lines().flat_map(|x| x.split(',')).map(hash).sum())
}
pub fn part_two(input: &str) -> Option<usize> {
let mut map = vec![Vec::<(&str, usize)>::new(); 256];
for instruction in input.lines().flat_map(|x| x.split(',')) {
let (label, n) = instruction.split_once(|x| x == '-' || x == '=').unwrap();
let hash = hash(label);
let indexed_map = map.get_mut(hash).unwrap();
if instruction.contains('-') {
if let Some((i, _)) = indexed_map.iter().enumerate().find(|(_, &x)| x.0 == label) {
indexed_map.remove(i);
}
} else {
let nbr = n.parse().unwrap();
if let Some((i, _)) = indexed_map.iter().enumerate().find(|(_, &x)| x.0 == label) {
indexed_map[i] = (label, nbr);
} else {
indexed_map.push((label, nbr))
}
}
}
Some(
map.into_iter()
.enumerate()
.flat_map(|(i, x)| {
x.into_iter()
.enumerate()
.map(move |(j, y)| (i + 1) * (j + 1) * y.1)
})
.sum(),
)
}
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(1320)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 15)),
Some(145)
);
}
}

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

@@ -0,0 +1,173 @@
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
fn next(&self, (y, x): (usize, usize)) -> (usize, usize) {
use Direction::*;
match self {
Up => (y.checked_sub(1).unwrap_or(usize::MAX), x),
Down => (y + 1, x),
Left => (y, x.checked_sub(1).unwrap_or(usize::MAX)),
Right => (y, x + 1),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Mirror {
Vertical,
Horizontal,
EvenSymmetric,
OddSymmetric,
}
struct ParseMirrorError;
impl TryFrom<u8> for Mirror {
type Error = ParseMirrorError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
b'|' => Self::Vertical,
b'-' => Self::Horizontal,
b'\\' => Self::EvenSymmetric,
b'/' => Self::OddSymmetric,
_ => return Err(ParseMirrorError),
})
}
}
impl Mirror {
fn bounce(&self, d: &Direction) -> (Direction, Option<Direction>) {
use Direction::*;
use Mirror::*;
match self {
Vertical => match d {
Up | Down => (*d, None),
Left | Right => (Up, Some(Down)),
},
Horizontal => match d {
Up | Down => (Left, Some(Right)),
Left | Right => (*d, None),
},
EvenSymmetric => (
match d {
Up => Left,
Down => Right,
Left => Up,
Right => Down,
},
None,
),
OddSymmetric => (
match d {
Up => Right,
Down => Left,
Left => Down,
Right => Up,
},
None,
),
}
}
}
fn solve_with_start(layout: &[Vec<Option<Mirror>>], start: (usize, usize, Direction)) -> usize {
let mut queue = vec![start];
let mut visited = HashSet::new();
while let Some((y, x, d)) = queue.pop() {
let point = layout.get(y).and_then(|row| row.get(x));
if point.is_none() {
continue;
}
if !visited.insert((y, x, d)) {
continue;
}
if let Some(Some(m)) = point {
let (new_d, opt_new_d) = m.bounce(&d);
let (ny, nx) = new_d.next((y, x));
queue.push((ny, nx, new_d));
if let Some(od) = opt_new_d {
let (ony, onx) = od.next((y, x));
queue.push((ony, onx, od));
}
} else {
let (ny, nx) = d.next((y, x));
queue.push((ny, nx, d));
}
}
HashSet::<(usize, usize)>::from_iter(visited.into_iter().map(|(y, x, _)| (y, x))).len()
}
pub fn part_one(input: &str) -> Option<usize> {
let layout: Vec<Vec<_>> = input
.lines()
.map(|x| x.as_bytes().iter().map(|&x| x.try_into().ok()).collect())
.collect();
Some(solve_with_start(&layout, (0, 0, Direction::Right)))
}
pub fn part_two(input: &str) -> Option<usize> {
let layout: Vec<Vec<_>> = input
.lines()
.map(|x| x.as_bytes().iter().map(|&x| x.try_into().ok()).collect())
.collect();
let h = layout.len();
let w = layout[0].len();
let mut scores = Vec::with_capacity(2 * (h + w) + 1);
for hh in 0..h {
let left = solve_with_start(&layout, (hh, 0, Direction::Right));
let right = solve_with_start(&layout, (hh, w - 1, Direction::Left));
scores.push(left);
scores.push(right);
}
for ww in 0..w {
let downward = solve_with_start(&layout, (0, ww, Direction::Down));
let upward = solve_with_start(&layout, (h - 1, ww, Direction::Up));
scores.push(downward);
scores.push(upward);
}
scores.into_iter().max()
}
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(46)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 16)),
Some(51)
);
}
}

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

@@ -0,0 +1,192 @@
use std::{
collections::{BinaryHeap, HashMap},
ops::Neg,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
const ALL: [Direction; 4] = [
Direction::Up,
Direction::Down,
Direction::Left,
Direction::Right,
];
}
impl Neg for Direction {
type Output = Self;
fn neg(self) -> Self::Output {
use Direction::*;
match self {
Up => Down,
Down => Up,
Left => Right,
Right => Left,
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct State {
heat: usize,
position: (usize, usize),
direction: Direction,
steps: usize,
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for State {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.heat
.cmp(&self.heat)
.then_with(|| self.position.cmp(&other.position))
}
}
fn find_path(
grid: &[Vec<usize>],
start: (usize, usize),
target: (usize, usize),
min_steps: usize,
max_steps: usize,
) -> Option<usize> {
let valid_indexing = |y: usize, x: usize| grid.get(y).and_then(|row| row.get(x)).is_some();
let mut heap = BinaryHeap::new();
heap.push(State {
heat: 0,
position: (0, 0),
direction: Direction::Right,
steps: 0,
});
let mut heatmap = HashMap::new();
heatmap.insert((start, Direction::Down, 0), 0);
heatmap.insert((start, Direction::Up, 0), 0);
while let Some(State {
heat,
position,
direction,
steps,
}) = heap.pop()
{
if position == target {
return Some(heat);
}
if *heatmap
.get(&(position, direction, steps))
.unwrap_or(&usize::MAX)
< heat
{
continue;
}
let (y, x) = position;
for d in Direction::ALL.iter().filter(|&x| *x != -direction) {
if steps < min_steps && *d != direction {
continue;
}
let (ny, nx) = match d {
Direction::Up => (y.wrapping_sub(1), x),
Direction::Down => (y + 1, x),
Direction::Left => (y, x.wrapping_sub(1)),
Direction::Right => (y, x + 1),
};
if !valid_indexing(ny, nx) {
continue;
}
let new_steps = if *d == direction { steps + 1 } else { 1 };
if new_steps > max_steps {
continue;
}
let state = State {
heat: heat + grid[ny][nx],
position: (ny, nx),
direction: *d,
steps: new_steps,
};
if *heatmap
.get(&((ny, nx), *d, new_steps))
.unwrap_or(&usize::MAX)
> state.heat
{
heatmap.insert(((ny, nx), *d, new_steps), state.heat);
heap.push(state);
}
}
}
None
}
pub fn part_one(input: &str) -> Option<usize> {
let grid: Vec<Vec<_>> = input
.lines()
.map(|x| {
x.chars()
.filter_map(|x| x.to_digit(10).map(|x| x as usize))
.collect()
})
.collect();
let target = (grid.len() - 1, grid[0].len() - 1);
find_path(&grid, (0, 0), target, 0, 3)
}
pub fn part_two(input: &str) -> Option<usize> {
let grid: Vec<Vec<_>> = input
.lines()
.map(|x| {
x.chars()
.filter_map(|x| x.to_digit(10).map(|x| x as usize))
.collect()
})
.collect();
let target = (grid.len() - 1, grid[0].len() - 1);
find_path(&grid, (0, 0), target, 4, 10)
}
aoc::solution!(17);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 17)),
Some(102)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 17)),
Some(94)
);
}
}

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

@@ -0,0 +1,126 @@
use std::str::FromStr;
#[derive(Debug, Clone, Copy)]
enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Debug)]
struct ParseDirectionError;
impl FromStr for Direction {
type Err = ParseDirectionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use Direction::*;
Ok(match s {
"U" | "3" => Up,
"D" | "1" => Down,
"L" | "2" => Left,
"R" | "0" => Right,
_ => return Err(ParseDirectionError),
})
}
}
impl From<Direction> for (isize, isize) {
fn from(value: Direction) -> Self {
use Direction::*;
match value {
Up => (-1, 0),
Down => (1, 0),
Left => (0, -1),
Right => (0, 1),
}
}
}
fn get_area(border: &[(isize, isize)], border_length: isize) -> isize {
// get area with shoelace formula (trapezoid variant)
// https://en.wikipedia.org/wiki/Shoelace_formula
let mut shoelace: isize = 0;
for n in 0..border.len() {
let (y1, x1) = border[n];
let (y2, x2) = border[(n + 1) % border.len()];
shoelace += (y1 + y2) * (x1 - x2);
}
let area = shoelace / 2;
// get interior by inverting pick's theorem formula
// https://en.wikipedia.org/wiki/Pick%27s_theorem
let interior = area + 1 - border_length / 2;
interior + border_length
}
fn get_border(instructions: &[(Direction, isize)]) -> (Vec<(isize, isize)>, isize) {
let mut border = Vec::new();
let mut border_length = 0;
let (mut sy, mut sx) = (0, 0);
border.push((sy, sx));
for (d, l) in instructions.iter().copied() {
let (dy, dx) = d.into();
(sy, sx) = (sy + l * dy, sx + l * dx);
border.push((sy, sx));
border_length += l;
}
border.pop();
(border, border_length)
}
pub fn part_one(input: &str) -> Option<isize> {
let instructions: Vec<(Direction, isize)> = input
.lines()
.map(|line| {
let [d, l, _] = line.splitn(3, ' ').collect::<Vec<_>>().try_into().unwrap();
(d.parse().unwrap(), l.parse::<isize>().unwrap())
})
.collect();
let (border, border_length) = get_border(&instructions);
Some(get_area(&border, border_length))
}
fn join_option_tuple<T, U>((a, b): (Option<T>, Option<U>)) -> Option<(T, U)> {
Some((a?, b?))
}
pub fn part_two(input: &str) -> Option<isize> {
let instructions: Vec<(Direction, isize)> = input
.lines()
.filter_map(|line| line.split_once(" (#"))
.filter_map(|(_, h)| h.strip_suffix(')'))
.map(|h| h.split_at(h.len() - 1))
.map(|(hex, dir)| (dir.parse().ok(), isize::from_str_radix(hex, 16).ok()))
.filter_map(join_option_tuple)
.collect();
let (border, border_length) = get_border(&instructions);
Some(get_area(&border, border_length))
}
aoc::solution!(18);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 18)),
Some(62)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 18)),
Some(952408144115)
);
}
}

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

@@ -0,0 +1,387 @@
use std::{
collections::HashMap,
ops::{Index, IndexMut},
str::FromStr,
};
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
enum Gear {
X,
M,
A,
S,
}
#[derive(Debug)]
struct GearParseError;
impl TryFrom<&str> for Gear {
type Error = GearParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use Gear::*;
Ok(match value {
"x" => X,
"m" => M,
"a" => A,
"s" => S,
_ => return Err(GearParseError),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
enum Comparator {
Lt,
Gt,
}
#[derive(Debug)]
struct ComparatorParseError;
impl TryFrom<char> for Comparator {
type Error = ComparatorParseError;
fn try_from(value: char) -> Result<Self, Self::Error> {
use Comparator::*;
Ok(match value {
'<' => Lt,
'>' => Gt,
_ => return Err(ComparatorParseError),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
enum Resolver {
Accepted,
Rejected,
Delegated(String),
}
impl From<&str> for Resolver {
fn from(value: &str) -> Self {
use Resolver::*;
match value {
"A" => Accepted,
"R" => Rejected,
x => Delegated(x.to_string()),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
struct Workflow {
workflows: Vec<WorkflowInner>,
}
#[derive(Debug)]
struct ParseWorkflowError;
impl TryFrom<&str> for Workflow {
type Error = ParseWorkflowError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self {
workflows: value.split(',').filter_map(|x| x.try_into().ok()).collect(),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
enum WorkflowInner {
Resolver(Resolver),
Rule((Gear, Comparator, usize, Resolver)),
}
#[derive(Debug)]
struct ParseWorkflowInnerError;
impl TryFrom<&str> for WorkflowInner {
type Error = ParseWorkflowInnerError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if !value.contains(':') {
return Ok(WorkflowInner::Resolver(value.into()));
}
let (rest, resolver) = value.split_once(':').unwrap();
let resolver = resolver.into();
let (gear, number) = rest.split_once(|x| x == '<' || x == '>').unwrap();
let gear = gear.try_into().map_err(|_| ParseWorkflowInnerError)?;
let number = number.parse().map_err(|_| ParseWorkflowInnerError)?;
let comparator = if value.contains('<') { '<' } else { '>' };
let comparator = comparator.try_into().map_err(|_| ParseWorkflowInnerError)?;
Ok(WorkflowInner::Rule((gear, comparator, number, resolver)))
}
}
#[derive(Debug)]
struct Xmas {
x: usize,
m: usize,
a: usize,
s: usize,
}
#[derive(Debug)]
struct ParseXmasError;
impl FromStr for Xmas {
type Err = ParseXmasError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.strip_prefix('{').ok_or(ParseXmasError)?;
let s = s.strip_suffix('}').ok_or(ParseXmasError)?;
let xmas: Vec<_> = s
.split(',')
.filter_map(|x| x.split_once('='))
.map(|x| x.1)
.filter_map(|x| x.parse().ok())
.collect();
Ok(Self {
x: xmas[0],
m: xmas[1],
a: xmas[2],
s: xmas[3],
})
}
}
impl Index<Gear> for Xmas {
type Output = usize;
fn index(&self, index: Gear) -> &Self::Output {
use Gear::*;
match index {
X => &self.x,
M => &self.m,
A => &self.a,
S => &self.s,
}
}
}
impl IndexMut<Gear> for Xmas {
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
use Gear::*;
match index {
X => &mut self.x,
M => &mut self.m,
A => &mut self.a,
S => &mut self.s,
}
}
}
impl Xmas {
fn sum(&self) -> usize {
self.x + self.m + self.a + self.s
}
fn apply(&self, workflow: &Workflow) -> Resolver {
for w in workflow.workflows.iter() {
match w {
WorkflowInner::Resolver(x) => {
return x.clone();
}
WorkflowInner::Rule((g, c, n, r)) => {
let is_match = match c {
Comparator::Gt => self[*g] > *n,
Comparator::Lt => self[*g] < *n,
};
if is_match {
return r.clone();
}
}
}
}
unreachable!()
}
}
#[derive(Debug, Clone, Copy)]
struct XmasRange {
x: (usize, usize),
m: (usize, usize),
a: (usize, usize),
s: (usize, usize),
}
impl Index<Gear> for XmasRange {
type Output = (usize, usize);
fn index(&self, index: Gear) -> &Self::Output {
use Gear::*;
match index {
X => &self.x,
M => &self.m,
A => &self.a,
S => &self.s,
}
}
}
impl IndexMut<Gear> for XmasRange {
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
use Gear::*;
match index {
X => &mut self.x,
M => &mut self.m,
A => &mut self.a,
S => &mut self.s,
}
}
}
impl XmasRange {
fn size(&self) -> usize {
(self.x.1 - self.x.0 + 1)
* (self.m.1 - self.m.0 + 1)
* (self.a.1 - self.a.0 + 1)
* (self.s.1 - self.s.0 + 1)
}
fn divide(self, workflow: &Workflow) -> Vec<(Self, Resolver)> {
let mut processed = Vec::new();
let mut rest = vec![self];
for w in workflow.workflows.iter() {
let mut new_rest = Vec::new();
while let Some(xmas_range) = rest.pop() {
match w {
WorkflowInner::Resolver(r) => processed.push((xmas_range, r.clone())),
WorkflowInner::Rule((g, c, n, r)) => {
let compare = |x: usize, y: usize| match c {
Comparator::Gt => x > y,
Comparator::Lt => x < y,
};
let mut min_ok = usize::MAX;
let mut max_ok = usize::MIN;
let mut min_e = usize::MAX;
let mut max_e = usize::MIN;
for i in xmas_range[*g].0..=xmas_range[*g].1 {
if compare(i, *n) {
max_ok = if max_ok < i { i } else { max_ok };
min_ok = if min_ok > i { i } else { min_ok };
} else {
max_e = if max_e < i { i } else { max_e };
min_e = if min_e > i { i } else { min_e };
}
}
if min_e <= max_e {
let mut r = xmas_range;
r[*g] = (min_e, max_e);
new_rest.push(r);
}
if min_ok <= max_ok {
let mut p = xmas_range;
p[*g] = (min_ok, max_ok);
processed.push((p, r.clone()));
}
}
}
}
rest = new_rest;
}
processed
}
}
fn build_map(input: &str) -> HashMap<&str, Workflow> {
let mut map = HashMap::new();
for (s, w) in input
.split("\n\n")
.next()
.unwrap()
.lines()
.filter_map(|x| x.strip_suffix('}'))
.filter_map(|x| x.split_once('{'))
.filter_map(|(s, w)| w.try_into().ok().map(|x| (s, x)))
{
map.insert(s, w);
}
map
}
pub fn part_one(input: &str) -> Option<usize> {
let map = build_map(input);
let xmas_vec: Vec<Xmas> = input
.split("\n\n")
.nth(1)?
.lines()
.filter_map(|x| x.parse().ok())
.collect();
let mut total = 0;
'outer: for xmas in xmas_vec.into_iter() {
let mut workflow = &map["in"];
'apply: loop {
match xmas.apply(workflow) {
Resolver::Accepted => break 'apply,
Resolver::Rejected => continue 'outer,
Resolver::Delegated(x) => workflow = &map[x.as_str()],
}
}
total += xmas.sum();
}
Some(total)
}
pub fn part_two(input: &str) -> Option<usize> {
let map = build_map(input);
let mut finished = Vec::new();
let mut stack = vec![(
XmasRange {
x: (1, 4000),
m: (1, 4000),
a: (1, 4000),
s: (1, 4000),
},
Resolver::Delegated("in".to_string()),
)];
while let Some((xmas_range, resolver)) = stack.pop() {
match resolver {
Resolver::Accepted => finished.push(xmas_range),
Resolver::Rejected => {
continue;
}
Resolver::Delegated(x) => {
let workflow = &map[x.as_str()];
stack.append(&mut xmas_range.divide(workflow))
}
}
}
Some(finished.iter().map(XmasRange::size).sum())
}
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(19114)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 19)),
Some(167409079868000)
);
}
}

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

@@ -0,0 +1,206 @@
use std::{
collections::{HashMap, VecDeque},
str::FromStr,
};
use aoc::lcm;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Module {
Broadcaster,
FlipFlop(bool),
Conjuction,
Output,
}
#[derive(Debug)]
struct ParseModuleError;
impl FromStr for Module {
type Err = ParseModuleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"%" => Module::FlipFlop(false),
"&" => Module::Conjuction,
"b" => Module::Broadcaster,
_ => return Err(ParseModuleError),
})
}
}
#[derive(Debug)]
struct Node {
index: usize,
module: Module,
inputs: Vec<usize>,
outputs: Vec<usize>,
}
fn parse_input(input: &str) -> Vec<Node> {
let mut nodes = Vec::new();
let mut mapper: HashMap<String, usize> = HashMap::new();
let output = Node {
index: 0,
module: Module::Output,
inputs: Vec::new(),
outputs: Vec::new(),
};
nodes.push(output);
for line in input.lines() {
let (from, _) = line.split_once(" -> ").unwrap();
let (module, name) = from.split_at(1);
let module: Module = module.parse().unwrap();
*mapper.entry(name.to_string()).or_default() = nodes.len();
let l = Node {
module,
index: nodes.len(),
inputs: Vec::new(),
outputs: Vec::new(),
};
nodes.push(l);
}
for line in input.lines() {
let (from, to) = line.split_once(" -> ").unwrap();
let (_, name) = from.split_at(1);
let index = mapper[name];
for destination in to.split(", ") {
let to_index = *mapper.get(destination).unwrap_or(&0);
nodes[index].outputs.push(to_index);
nodes[to_index].inputs.push(index);
}
}
nodes
}
pub fn cycle(input: &str, button_presses: Option<usize>) -> usize {
let mut nodes = parse_input(input);
let mut graph = vec![vec![false; nodes.len()]; nodes.len()];
let mut highs = 0;
let mut lows = 0;
let broadcaster = nodes
.iter()
.find(|x| x.module == Module::Broadcaster)
.unwrap()
.index;
let mut hm = HashMap::new();
let critical_node = nodes[0].inputs.first().copied();
let critical_inputs = critical_node.map(|x| nodes[x].inputs.len());
for i in 1..=button_presses.unwrap_or(usize::MAX) {
if let Some(c) = critical_inputs {
if button_presses.is_none() && hm.values().len() == c {
return hm.values().copied().reduce(lcm).unwrap_or(0);
}
}
let mut stack = VecDeque::from([(broadcaster, false)]);
while let Some((index, signal)) = stack.pop_front() {
if signal {
highs += 1;
} else {
lows += 1;
}
let node = &nodes[index];
let mut new_module = None;
match node.module {
Module::Output => (),
Module::Broadcaster => {
for dest_index in node.outputs.iter().copied() {
stack.push_back((dest_index, false));
}
}
Module::FlipFlop(high) => {
if !signal {
let signal = !high;
for dest_index in nodes[index].outputs.iter().copied() {
graph[index][dest_index] = signal;
stack.push_back((dest_index, signal));
}
new_module = Some(Module::FlipFlop(signal));
}
}
Module::Conjuction => {
let mut all = true;
for in_index in nodes[index].inputs.iter().copied() {
all &= graph[in_index][index];
}
for dest_index in nodes[index].outputs.iter().copied() {
graph[index][dest_index] = !all;
stack.push_back((dest_index, !all));
}
if let Some(c) = critical_node {
if index == c {
for in_index in nodes[index].inputs.iter().copied() {
if graph[in_index][index] {
hm.entry(in_index).or_insert(i);
}
}
}
}
}
}
let node = &mut nodes[index];
if let Some(module) = new_module {
node.module = module;
}
}
}
highs * lows
}
pub fn part_one(input: &str) -> Option<usize> {
Some(cycle(input, Some(1000)))
}
pub fn part_two(input: &str) -> Option<usize> {
Some(cycle(input, None))
}
aoc::solution!(20);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one_first() {
assert_eq!(
part_one(&aoc::template::read_file_part("examples", 20, 1)),
Some(32000000)
);
}
#[test]
fn test_part_one_second() {
assert_eq!(
part_one(&aoc::template::read_file_part("examples", 20, 2)),
Some(11687500)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file_part("examples", 20, 2)),
Some(1)
);
}
}

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

@@ -0,0 +1,106 @@
use std::{collections::HashSet, usize};
fn parse_input(input: &str) -> (Vec<Vec<u8>>, (isize, isize)) {
let mut grid: Vec<_> = input.lines().map(|x| x.as_bytes().to_vec()).collect();
let start = grid
.iter()
.enumerate()
.flat_map(|(y, item)| item.iter().enumerate().map(move |(x, item)| (x, y, *item)))
.find_map(|(x, y, s)| if s == b'S' { Some((y, x)) } else { None })
.unwrap();
grid[start.0][start.1] = b'.';
(grid, (start.0 as isize, start.1 as isize))
}
fn walk_return_at(
grid: &[Vec<u8>],
start: (isize, isize),
mut returns: Vec<usize>,
can_cycle: bool,
) -> Vec<usize> {
returns.sort_by(|a, b| b.cmp(a));
let h = grid.len() as isize;
let w = grid[0].len() as isize;
let invalid_indexing = |y, x| y < 0 || y >= h || x < 0 || x >= w;
let mut results = Vec::new();
let length = returns[0];
let mut next = returns.pop().unwrap();
let mut visited = HashSet::new();
visited.insert(start);
for i in 1..=length {
let mut new_visited = HashSet::new();
for (y, x) in visited.iter() {
for (dy, dx) in [(1, 0), (0, 1), (-1, 0), (0, -1)] {
let (ny, nx) = (y + dy, x + dx);
if !can_cycle && invalid_indexing(ny, nx) {
continue;
}
let (cy, cx) = (ny.rem_euclid(h) as usize, nx.rem_euclid(w) as usize);
if grid[cy][cx] == b'.' {
new_visited.insert((ny, nx));
}
}
}
visited = new_visited;
if i == next {
results.push(visited.len());
if !returns.is_empty() {
next = returns.pop().unwrap();
}
}
}
results
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, start) = parse_input(input);
let result = walk_return_at(&grid, start, vec![64], false);
Some(result[0])
}
pub fn part_two(input: &str) -> Option<usize> {
let (grid, start) = parse_input(input);
let h = grid.len();
let s = start.0 as usize;
let result = walk_return_at(&grid, start, vec![s, s + h, s + 2 * h], true);
let a = (result[2] - 2 * result[1] + result[0]) / 2;
let b = (result[1] - result[0]) - a;
let c = result[0];
let x = 26501365 / h;
Some(a * x * x + b * x + c)
}
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(42)
);
}
}

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

@@ -0,0 +1,143 @@
use std::{collections::HashMap, str::FromStr};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Brick {
from: (usize, usize, usize),
to: (usize, usize, usize),
}
#[derive(Debug)]
struct ParseBrickError;
impl FromStr for Brick {
type Err = ParseBrickError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (first, second) = s.split_once('~').ok_or(ParseBrickError)?;
let sorted: Vec<_> = first
.split(',')
.filter_map(|y| y.parse().ok())
.zip(second.split(',').filter_map(|y| y.parse().ok()))
.map(|(x, y)| if x < y { (x, y) } else { (y, x) })
.collect();
let from = (sorted[0].0, sorted[1].0, sorted[2].0);
let to = (sorted[0].1, sorted[1].1, sorted[2].1);
Ok(Self { from, to })
}
}
impl Brick {
fn height(&self) -> usize {
self.to.2 - self.from.2 + 1
}
fn points(&self) -> impl Iterator<Item = (usize, usize, usize)> + '_ {
(self.from.0..=self.to.0).flat_map(move |x| {
(self.from.1..=self.to.1)
.flat_map(move |y| (self.from.2..=self.to.2).map(move |z| (x, y, z)))
})
}
}
impl PartialOrd for Brick {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Brick {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.from
.2
.cmp(&other.from.2)
.then((self.from.0, self.from.1).cmp(&(other.from.0, other.from.1)))
}
}
#[derive(Debug, Clone)]
struct Tower {
bricks: Vec<Brick>,
}
impl Tower {
fn new(mut bricks: Vec<Brick>) -> Self {
bricks.sort();
let mut ret = Self { bricks };
ret.compress(None);
ret
}
fn compress(&mut self, skip: Option<usize>) -> usize {
let mut heights: HashMap<(usize, usize), usize> = HashMap::new();
let mut moved = 0;
for brick in self
.bricks
.iter_mut()
.enumerate()
.filter(|(i, _)| *i != skip.unwrap_or(usize::MAX))
.map(|(_, x)| x)
{
let height = brick.height();
let new_height = brick
.points()
.map(|(x, y, _)| *heights.entry((x, y)).or_default())
.max()
.unwrap();
for (x, y, _) in brick.points() {
*heights.get_mut(&(x, y)).unwrap() = new_height + height;
}
if new_height + 1 == brick.from.2 {
continue;
}
if skip.is_none() {
brick.from.2 = new_height + 1;
brick.to.2 = new_height + height;
}
moved += 1;
}
moved
}
}
pub fn part_one(input: &str) -> Option<usize> {
let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect());
Some(
(0..tower.bricks.len())
.map(|i| tower.compress(Some(i)))
.filter(|m| *m == 0)
.count(),
)
}
pub fn part_two(input: &str) -> Option<usize> {
let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect());
Some(
(0..tower.bricks.len())
.map(|i| tower.compress(Some(i)))
.sum(),
)
}
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(5));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 22)), Some(7));
}
}

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

@@ -0,0 +1,161 @@
use std::{
collections::{HashMap, HashSet},
iter::once,
};
const DIRS: [(isize, isize); 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)];
#[derive(Debug, Default)]
struct Node {
neighbours: Vec<(usize, usize)>,
}
fn build_graph(input: &str, two_way: bool) -> (Vec<Node>, usize) {
let mut grid: Vec<Vec<_>> = input.lines().map(|x| x.as_bytes().to_vec()).collect();
let l = grid[0].len();
let border: Vec<_> = once(b'#').cycle().take(l).collect();
grid.insert(0, border.clone());
grid.push(border);
let start = (1, 1);
let end = (grid.len() - 2, grid[0].len() - 2);
let mut nodes = Vec::new();
let mut mapper = HashMap::new();
mapper.insert(start, 0);
let mut visited: HashSet<(usize, usize)> = HashSet::new();
let mut stack = Vec::new();
// (from_node, at, len)
stack.push((start, start, 0));
let forced = |x| match x {
b'>' => (0, 1),
b'v' => (1, 0),
_ => (0, 0),
};
while let Some((from, (y, x), len)) = stack.pop() {
let is_node = DIRS
.iter()
.filter(|(dy, dx)| {
grid[((y as isize) + dy) as usize][((x as isize) + dx) as usize] != b'#'
})
.count()
> 2
|| (y, x) == start
|| (y, x) == end;
if is_node {
if !visited.contains(&(y, x)) {
mapper.insert((y, x), nodes.len());
nodes.push(Node::default());
}
if from != (y, x) {
nodes[mapper[&from]].neighbours.push((mapper[&(y, x)], len));
if two_way {
nodes[mapper[&(y, x)]].neighbours.push((mapper[&from], len));
}
}
}
if !visited.insert((y, x)) {
continue;
}
for (ny, nx, _, _) in DIRS
.iter()
.map(|(dy, dx)| {
(
((y as isize) + dy) as usize,
((x as isize) + dx) as usize,
dy,
dx,
)
})
.filter(|(ny, nx, _, _)| grid[*ny][*nx] != b'#')
.filter(|(ny, nx, dy, dx)| {
let (fy, fx) = forced(grid[*ny][*nx]);
(fy + **dy, fx + **dx) != (0, 0)
})
{
let new_len = if is_node { 1 } else { len + 1 };
let new_from = if is_node { (y, x) } else { from };
stack.push((new_from, (ny, nx), new_len));
}
}
(nodes, mapper[&end])
}
fn longest_path(
nodes: &[Node],
mut visited: usize,
location: usize,
target: usize,
length: usize,
) -> Option<usize> {
if location == target {
return Some(length);
}
// binary mask for visited since < 64 nodes in input
// nth bit tells if location n was visited already
visited |= 1 << location;
let mut max_len = 0;
for (n, l) in nodes[location]
.neighbours
.iter()
.copied()
.filter(|(n, _)| (visited & 1 << n) == 0)
{
if let Some(new_len) = longest_path(nodes, visited, n, target, length + l) {
if max_len < new_len {
max_len = new_len;
}
}
}
if max_len > 0 {
Some(max_len)
} else {
None
}
}
pub fn part_one(input: &str) -> Option<usize> {
let (nodes, target) = build_graph(input, false);
longest_path(&nodes, 0, 0, target, 0)
}
pub fn part_two(input: &str) -> Option<usize> {
let (nodes, target) = build_graph(input, true);
longest_path(&nodes, 0, 0, target, 0)
}
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(94)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 23)),
Some(154)
);
}
}

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

@@ -0,0 +1,94 @@
use z3::ast::{Ast, Int};
type Vector3 = (f64, f64, f64);
fn parse_input(input: &str) -> Vec<(Vector3, Vector3)> {
input
.lines()
.filter_map(|x| x.split_once(" @ "))
.map(|(a, b)| {
let av: Vec<_> = a.splitn(3, ", ").filter_map(|x| x.parse().ok()).collect();
let bv: Vec<_> = b.splitn(3, ", ").filter_map(|x| x.parse().ok()).collect();
((av[0], av[1], av[2]), (bv[0], bv[1], bv[2]))
})
.collect()
}
fn part_one_with_area(input: &str, min: f64, max: f64) -> u32 {
let points = parse_input(input);
let mut c = 0;
let test_area = |x: f64, y: f64| x <= max && x >= min && y <= max && y >= min;
for (i, ((x1, y1, _), (dx1, dy1, _))) in points.iter().copied().enumerate() {
for ((x2, y2, _), (dx2, dy2, _)) in points.iter().skip(i + 1).copied() {
let n2 = (x2 - x1 + (y1 - y2) * dx1 / dy1) / (dy2 * dx1 / dy1 - dx2);
let n1 = (x1 - x2 + (y2 - y1) * dx2 / dy2) / (dy1 * dx2 / dy2 - dx1);
if n1 <= 0.0 || n2 <= 0.0 {
continue;
}
let x = x1 + n1 * dx1;
let y = y1 + n1 * dy1;
if test_area(x, y) {
c += 1;
}
}
}
c
}
pub fn part_one(input: &str) -> Option<u32> {
let min = 200000000000000.0;
let max = 400000000000000.0;
Some(part_one_with_area(input, min, max))
}
pub fn part_two(input: &str) -> Option<u64> {
let points = parse_input(input);
let ctx = z3::Context::new(&z3::Config::new());
let solver = z3::Solver::new(&ctx);
let zero = Int::from_u64(&ctx, 0);
let [a, b, c, da, db, dc] = ["a", "b", "c", "da", "db", "dc"].map(|x| Int::new_const(&ctx, x));
for (i, ((x, y, z), (dx, dy, dz))) in points.iter().enumerate().take(3) {
let [x, y, z, dx, dy, dz] = [x, y, z, dx, dy, dz].map(|w| Int::from_i64(&ctx, *w as i64));
let t = Int::new_const(&ctx, format!("t_{i}").as_str());
solver.assert(&t.ge(&zero));
solver.assert(&((&x + &dx * &t)._eq(&(&a + &da * &t))));
solver.assert(&((&y + &dy * &t)._eq(&(&b + &db * &t))));
solver.assert(&((&z + &dz * &t)._eq(&(&c + &dc * &t))));
}
solver.check();
let model = solver.get_model().unwrap();
let res = model.eval(&(&a + &b + &c), true).unwrap();
res.as_u64()
}
aoc::solution!(24);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
let input = aoc::template::read_file("examples", 24);
let min = 7.0;
let max = 27.0;
assert_eq!(part_one_with_area(&input, min, max), 2);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 24)),
Some(47)
);
}
}

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

@@ -0,0 +1,80 @@
use std::collections::HashMap;
fn build_graph(input: &str, skips: &[(&str, &str)]) -> Vec<Vec<usize>> {
let mut nodes = Vec::new();
let mut mapper = HashMap::new();
for (node, neighs) in input.lines().filter_map(|x| x.split_once(": ")) {
if !mapper.contains_key(node) {
mapper.insert(node, nodes.len());
nodes.push(Vec::new());
}
let index_a = mapper[node];
for n in neighs.split(' ') {
if !mapper.contains_key(n) {
mapper.insert(n, nodes.len());
nodes.push(Vec::new());
}
if skips.contains(&(node, n)) || skips.contains(&(n, node)) {
continue;
}
let index_b = mapper[n];
nodes[index_a].push(index_b);
nodes[index_b].push(index_a);
}
}
nodes
}
pub fn part_one_wrapped(input: &str, skips: &[(&str, &str)]) -> Option<usize> {
let nodes = build_graph(input, skips);
let mut visited = vec![false; nodes.len()];
let mut stack = vec![0];
while let Some(node) = stack.pop() {
if visited[node] {
continue;
}
visited[node] = true;
for n in nodes[node].iter().copied() {
if !visited[n] {
stack.push(n);
}
}
}
let counter = visited.iter().copied().filter(|&x| x).count();
Some((nodes.len() - counter) * counter)
}
pub fn part_one(input: &str) -> Option<usize> {
// to figure out which nodes to skip
// echo "graph A {\n$(cat data/inputs/25.txt)\n}" | \
// sed 's/\(.*\): \(.*\)$/\1 -- {\2}/' | \
// dot -Tsvg -Kneato > graph.svg
let skips = [("sfm", "vmt"), ("vph", "mfc"), ("fql", "rmg")];
part_one_wrapped(input, &skips)
}
pub fn part_two(_input: &str) -> Option<String> {
Some("Happy chrismas!".into())
}
aoc::solution!(25);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
let skips = [("pzl", "hfx"), ("bvb", "cmg"), ("nvd", "jqt")];
let input = aoc::template::read_file("examples", 25);
assert_eq!(part_one_wrapped(&input, &skips), Some(54));
}
}