Skip to content

Commit 1440b3b

Browse files
committed
Year 2024 Day 21
1 parent 09a10a1 commit 1440b3b

File tree

7 files changed

+174
-4
lines changed

7 files changed

+174
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
9292
| 18 | [RAM Run](https://adventofcode.com/2024/day/18) | [Source](src/year2024/day18.rs) | 42 |
9393
| 19 | [Linen Layout](https://adventofcode.com/2024/day/19) | [Source](src/year2024/day19.rs) | 118 |
9494
| 20 | [Race Condition](https://adventofcode.com/2024/day/20) | [Source](src/year2024/day20.rs) | 1354 |
95+
| 21 | [Keypad Conundrum](https://adventofcode.com/2024/day/21) | [Source](src/year2024/day21.rs) | 184 |
9596
| 22 | [Monkey Market](https://adventofcode.com/2024/day/22) | [Source](src/year2024/day22.rs) | 1350 |
9697
| 23 | [LAN Party](https://adventofcode.com/2024/day/23) | [Source](src/year2024/day23.rs) | 43 |
9798

benches/benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,5 @@ benchmark!(year2023
8888

8989
benchmark!(year2024
9090
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
91-
day14, day15, day16, day17, day18, day19, day20, day22, day23
91+
day14, day15, day16, day17, day18, day19, day20, day21, day22, day23
9292
);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,5 @@ library!(year2023 "Restore global snow production."
6868

6969
library!(year2024 "Locate the Chief Historian in time for the big Christmas sleigh launch."
7070
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
71-
day14, day15, day16, day17, day18, day19, day20, day22, day23
71+
day14, day15, day16, day17, day18, day19, day20, day21, day22, day23
7272
);

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,5 @@ run!(year2023
138138

139139
run!(year2024
140140
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
141-
day14, day15, day16, day17, day18, day19, day20, day22, day23
141+
day14, day15, day16, day17, day18, day19, day20, day21, day22, day23
142142
);

src/year2024/day21.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::util::hash::*;
2+
use crate::util::parse::*;
3+
use crate::util::point::*;
4+
5+
type Cache = FastMap<(Vec<u8>, usize), usize>;
6+
7+
pub fn parse(input: &str) -> &str {
8+
input
9+
}
10+
11+
pub fn part1(input: &str) -> usize {
12+
chain(input, 3)
13+
}
14+
15+
pub fn part2(input: &str) -> usize {
16+
chain(input, 26)
17+
}
18+
19+
fn chain(input: &str, limit: usize) -> usize {
20+
let cache = &mut FastMap::with_capacity(500);
21+
input
22+
.lines()
23+
.map(str::as_bytes)
24+
.zip(input.iter_unsigned::<usize>())
25+
.map(|(code, numeric)| dfs(cache, code, 0, limit) * numeric)
26+
.sum()
27+
}
28+
29+
fn dfs(cache: &mut Cache, slice: &[u8], depth: usize, limit: usize) -> usize {
30+
if depth == limit {
31+
return slice.len();
32+
}
33+
34+
let key = (slice.to_vec(), depth);
35+
if let Some(&previous) = cache.get(&key) {
36+
return previous;
37+
}
38+
39+
let mut shortest = usize::MAX;
40+
let keypad = if depth == 0 { NUMERIC } else { DIRECTIONAL };
41+
42+
for sequence in combinations(slice, &keypad) {
43+
let mut presses = 0;
44+
45+
for chunk in sequence.split_inclusive(|&b| b == b'A') {
46+
presses += dfs(cache, chunk, depth + 1, limit);
47+
}
48+
49+
shortest = shortest.min(presses);
50+
}
51+
52+
cache.insert(key, shortest);
53+
shortest
54+
}
55+
56+
fn combinations(current: &[u8], keypad: &Keypad) -> Vec<Vec<u8>> {
57+
let mut next = Vec::new();
58+
pad_dfs(&mut next, &mut Vec::new(), keypad, current, 0, keypad.start);
59+
next
60+
}
61+
62+
fn pad_dfs(
63+
combinations: &mut Vec<Vec<u8>>,
64+
path: &mut Vec<u8>,
65+
keypad: &Keypad,
66+
sequence: &[u8],
67+
depth: usize,
68+
from: Point,
69+
) {
70+
// Success
71+
if depth == sequence.len() {
72+
combinations.push(path.clone());
73+
return;
74+
}
75+
76+
// Failure
77+
if from == keypad.gap {
78+
return;
79+
}
80+
81+
let to = keypad.lookup[sequence[depth] as usize];
82+
83+
if from == to {
84+
// Push button.
85+
path.push(b'A');
86+
pad_dfs(combinations, path, keypad, sequence, depth + 1, from);
87+
path.pop();
88+
} else {
89+
// Move towards button.
90+
let mut step = |next: u8, direction: Point| {
91+
path.push(next);
92+
pad_dfs(combinations, path, keypad, sequence, depth, from + direction);
93+
path.pop();
94+
};
95+
96+
if to.x < from.x {
97+
step(b'<', LEFT);
98+
}
99+
if to.x > from.x {
100+
step(b'>', RIGHT);
101+
}
102+
if to.y < from.y {
103+
step(b'^', UP);
104+
}
105+
if to.y > from.y {
106+
step(b'v', DOWN);
107+
}
108+
}
109+
}
110+
111+
struct Keypad {
112+
start: Point,
113+
gap: Point,
114+
lookup: [Point; 128],
115+
}
116+
117+
const NUMERIC: Keypad = {
118+
let start = Point::new(2, 3);
119+
let gap = Point::new(0, 3);
120+
let mut lookup = [ORIGIN; 128];
121+
122+
lookup[b'7' as usize] = Point::new(0, 0);
123+
lookup[b'8' as usize] = Point::new(1, 0);
124+
lookup[b'9' as usize] = Point::new(2, 0);
125+
lookup[b'4' as usize] = Point::new(0, 1);
126+
lookup[b'5' as usize] = Point::new(1, 1);
127+
lookup[b'6' as usize] = Point::new(2, 1);
128+
lookup[b'1' as usize] = Point::new(0, 2);
129+
lookup[b'2' as usize] = Point::new(1, 2);
130+
lookup[b'3' as usize] = Point::new(2, 2);
131+
lookup[b'0' as usize] = Point::new(1, 3);
132+
lookup[b'A' as usize] = Point::new(2, 3);
133+
134+
Keypad { start, gap, lookup }
135+
};
136+
137+
const DIRECTIONAL: Keypad = {
138+
let start = Point::new(2, 0);
139+
let gap = Point::new(0, 0);
140+
let mut lookup = [ORIGIN; 128];
141+
142+
lookup[b'^' as usize] = Point::new(1, 0);
143+
lookup[b'A' as usize] = Point::new(2, 0);
144+
lookup[b'<' as usize] = Point::new(0, 1);
145+
lookup[b'v' as usize] = Point::new(1, 1);
146+
lookup[b'>' as usize] = Point::new(2, 1);
147+
148+
Keypad { start, gap, lookup }
149+
};

tests/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,5 @@ test!(year2023
8181

8282
test!(year2024
8383
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
84-
day14, day15, day16, day17, day18, day19, day20, day22, day23
84+
day14, day15, day16, day17, day18, day19, day20, day21, day22, day23
8585
);

tests/year2024/day21.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use aoc::year2024::day21::*;
2+
3+
const EXAMPLE: &str = "\
4+
029A
5+
980A
6+
179A
7+
456A
8+
379A";
9+
10+
#[test]
11+
fn part1_test() {
12+
let input = parse(EXAMPLE);
13+
assert_eq!(part1(input), 126384);
14+
}
15+
16+
#[test]
17+
fn part2_test() {
18+
let input = parse(EXAMPLE);
19+
assert_eq!(part2(input), 154115708116294);
20+
}

0 commit comments

Comments
 (0)