Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e42e738

Browse files
committedDec 20, 2024·
aoc: day20
1 parent a73012b commit e42e738

File tree

2 files changed

+199
-3
lines changed

2 files changed

+199
-3
lines changed
 

‎adventofcode/2024/20.rs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
use std::cmp::Ordering;
2+
use std::collections::{BinaryHeap, HashMap};
3+
use std::env;
4+
use std::io::{self, BufRead};
5+
use std::ops::Deref;
6+
use std::rc::Rc;
7+
8+
type Pos = (usize, usize);
9+
10+
#[derive(Clone, Copy, PartialEq, Eq)]
11+
enum Cell {
12+
Track,
13+
Wall,
14+
}
15+
16+
#[derive(Default)]
17+
struct PathNode(Pos, Option<Rc<PathNode>>);
18+
type Path = Rc<PathNode>;
19+
20+
impl PathNode {
21+
fn to_vec(&self) -> Vec<Pos> {
22+
let mut v = vec![self.0];
23+
let mut cur = self.1.as_ref();
24+
while let Some(PathNode(pos, next)) = cur.map(|o| o.deref()) {
25+
v.push(*pos);
26+
cur = next.as_ref();
27+
}
28+
v.reverse();
29+
v
30+
}
31+
}
32+
33+
struct Maze {
34+
cells: Vec<Vec<Cell>>,
35+
start: Pos,
36+
end: Pos,
37+
width: usize,
38+
height: usize,
39+
cache: HashMap<Pos, usize>,
40+
}
41+
42+
impl Maze {
43+
fn parse(lines: impl Iterator<Item = String>) -> Self {
44+
let mut start = (0, 0);
45+
let mut end = (0, 0);
46+
let cells: Vec<Vec<_>> = lines
47+
.enumerate()
48+
.map(|(y, l)| {
49+
l.chars()
50+
.enumerate()
51+
.map(|(x, c)| match c {
52+
'S' => {
53+
start = (x, y);
54+
Cell::Track
55+
}
56+
'E' => {
57+
end = (x, y);
58+
Cell::Track
59+
}
60+
'.' => Cell::Track,
61+
'#' => Cell::Wall,
62+
_ => unreachable!(),
63+
})
64+
.collect()
65+
})
66+
.collect();
67+
let width = cells[0].len();
68+
let height = cells.len();
69+
Self { cells, start, end, width, height, cache: HashMap::new() }
70+
}
71+
72+
fn memoize(&mut self, mut steps: usize, mut cur: &Path) {
73+
loop {
74+
let PathNode(pos, next) = &**cur;
75+
self.cache.insert(*pos, steps);
76+
if let Some(next) = next {
77+
cur = next;
78+
steps += 1;
79+
} else {
80+
return;
81+
}
82+
}
83+
}
84+
85+
fn path(&mut self, start: Pos) -> Option<(usize, Option<Path>)> {
86+
if let Some(&ret) = self.cache.get(&start) {
87+
return Some((ret, None));
88+
}
89+
90+
struct State {
91+
pos: Pos,
92+
steps: usize,
93+
path: Path,
94+
}
95+
96+
impl Ord for State {
97+
fn cmp(&self, other: &Self) -> Ordering {
98+
other.steps.cmp(&self.steps)
99+
}
100+
}
101+
102+
impl PartialOrd for State {
103+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
104+
Some(self.cmp(other))
105+
}
106+
}
107+
108+
impl PartialEq for State {
109+
fn eq(&self, other: &Self) -> bool {
110+
self.cmp(other) == Ordering::Equal
111+
}
112+
}
113+
114+
impl Eq for State {}
115+
116+
let mut queue = BinaryHeap::from([State {
117+
pos: start,
118+
steps: 0,
119+
path: Rc::new(PathNode(start, None)),
120+
}]);
121+
let mut dist = HashMap::from([(start, 0)]);
122+
123+
while let Some(State { pos, steps, path }) = queue.pop() {
124+
if let Some(&steps) = self.cache.get(&pos) {
125+
self.memoize(steps, &path);
126+
return Some((steps, None));
127+
}
128+
129+
if pos == self.end {
130+
self.memoize(0, &path);
131+
return Some((steps, Some(path)));
132+
}
133+
if let Some(s) = dist.get(&pos) {
134+
if *s < steps {
135+
continue;
136+
}
137+
}
138+
let (x, y) = pos;
139+
let steps = steps + 1;
140+
for pos in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] {
141+
let (x, y) = pos;
142+
if self.cells[y][x] == Cell::Track
143+
&& dist.get(&pos).map(|&s| steps < s).unwrap_or(true)
144+
{
145+
let path = Rc::new(PathNode(pos, Some(path.clone())));
146+
dist.insert(pos, steps);
147+
queue.push(State { pos, steps, path });
148+
}
149+
}
150+
}
151+
152+
None
153+
}
154+
155+
fn solve(&mut self, max_dist: usize) -> usize {
156+
let (orig, path) = self.path(self.start).unwrap();
157+
let path = path.unwrap().to_vec();
158+
159+
let mut count = 0;
160+
for (start, (ox, oy)) in path.into_iter().enumerate() {
161+
for x in ox.saturating_sub(max_dist)..(ox + max_dist + 1).min(self.width) {
162+
for y in oy.saturating_sub(max_dist)..(oy + max_dist + 1).min(self.height) {
163+
let dist = x.abs_diff(ox) + y.abs_diff(oy);
164+
if 2 <= dist && dist <= max_dist && self.cells[y][x] == Cell::Track {
165+
if let Some((extra, _)) = self.path((x, y)) {
166+
let steps = start + extra + dist; // Two steps for cheating through the wall
167+
if orig > steps && orig - steps >= 100 {
168+
count += 1;
169+
}
170+
}
171+
}
172+
}
173+
}
174+
}
175+
count
176+
}
177+
}
178+
179+
fn part1(lines: impl Iterator<Item = String>) {
180+
let mut maze = Maze::parse(lines);
181+
println!("{}", maze.solve(2));
182+
}
183+
184+
fn part2(lines: impl Iterator<Item = String>) {
185+
let mut maze = Maze::parse(lines);
186+
println!("{}", maze.solve(20));
187+
}
188+
189+
fn main() {
190+
let lines = io::stdin().lock().lines().map(Result::unwrap);
191+
match env::args().nth(1).as_deref() {
192+
Some("1") => part1(lines),
193+
Some("2") => part2(lines),
194+
x => panic!("invalid argument: {:?}", x),
195+
}
196+
}

‎adventofcode/2024/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ path = "18.rs"
8080
name = "19"
8181
path = "19.rs"
8282

83-
# [[bin]]
84-
# name = "20"
85-
# path = "20.rs"
83+
[[bin]]
84+
name = "20"
85+
path = "20.rs"
8686

8787
# [[bin]]
8888
# name = "21"

0 commit comments

Comments
 (0)
Please sign in to comment.