Skip to content

Commit a48877c

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

File tree

7 files changed

+182
-4
lines changed

7 files changed

+182
-4
lines changed

README.md

+1
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) | 111 |
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

+1-1
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

+1-1
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

+1-1
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

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

tests/test.rs

+1-1
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

+20
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)