summaryrefslogtreecommitdiffstats
path: root/src/day_17.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/day_17.rs')
-rw-r--r--src/day_17.rs163
1 files changed, 101 insertions, 62 deletions
diff --git a/src/day_17.rs b/src/day_17.rs
index 0ba3be8..728e1d8 100644
--- a/src/day_17.rs
+++ b/src/day_17.rs
@@ -23,7 +23,7 @@ impl Solution for Day17 {
fn part_1(input: &str) -> anyhow::Result<Self::Answer1> {
let grid = parse_grid(input).context("Failed to parse grid")?;
- astar::<0, 3>(&grid, (0, 0), (grid.len() - 1, grid[0].len() - 1))
+ astar::<1, 3>(&grid, (0, 0), (grid.len() - 1, grid[0].len() - 1))
.context("Failed to find path")
// 755 ==
@@ -50,31 +50,64 @@ fn parse_grid(input: &str) -> Option<Grid> {
.try_collect::<Grid>()
}
-fn astar<const M: u8, const N: u8>(grid: &Grid, start: Position, goal: Position) -> Option<usize> {
- let mut queue = BinaryHeap::from([Reverse(Node::<M, N>::start(start))]);
- let mut cache = HashMap::from([((start, vec![]), usize::MAX)]);
+#[cfg(test)]
+fn rebuild_path(edges: &[Edge], start: Position, end: Position) -> Vec<Position> {
+ let mut path = vec![];
+ let mut position = start;
+ for edge in edges {
+ path.push(position);
+ position = edge.traverse(position).unwrap();
+ }
+ path.push(end);
+ path
+}
+
+fn astar<const S: usize, const M: usize>(
+ grid: &Grid,
+ start: Position,
+ goal: Position,
+) -> Option<usize> {
+ let mut queue = BinaryHeap::from([Reverse(Node::<S, M>::start(start))]);
+ let mut cache = HashMap::from([((start, None), usize::MAX)]);
+
+ #[cfg(test)]
+ let printer = &mut crate::Printer::from(std::io::stdout().lock());
while let Some(Reverse(node)) = queue.pop() {
- for (edge, position) in node.successors(grid) {
+ for (edges, position, cost) in node.successors(grid) {
let mut next = node.clone();
next.position = position;
- next.path.push(edge);
- next.cost += grid[position.0][position.1];
+ next.path.extend(edges);
+ next.cost += cost;
next.estimated = next.cost + next.dist(goal);
- if next.goal(goal) {
+ #[cfg(test)]
+ {
+ let node = &next;
+ let path = rebuild_path(&node.path, start, node.position);
+ printer
+ .with(format_args!("pos: {:?}", node.position))
+ .with(format_args!("score: {}", node.cost))
+ .with(format_args!("trail: {:?}", node.trail()))
+ .with_grid(grid, &path)
+ .clear()
+ .write()
+ .unwrap();
+ }
+
+ // pause();
+ if next.position == goal {
+ #[cfg(test)]
+ printer
+ .with(format_args!("score: {}", node.cost))
+ .with_grid(grid, &rebuild_path(&next.path, start, next.position))
+ .clear()
+ .write()
+ .unwrap();
return Some(next.cost);
}
- let trail = next
- .path
- .iter()
- .copied()
- .rev()
- .take(N as usize)
- .rev()
- .collect::<Vec<_>>();
- match cache.entry((next.position, trail)) {
+ match cache.entry((next.position, next.trail())) {
Entry::Vacant(e) => {
e.insert(next.cost);
}
@@ -98,14 +131,14 @@ type Grid = Vec<Vec<usize>>;
type Position = (usize, usize);
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Node<const M: u8, const N: u8> {
+pub struct Node<const STEP: usize, const MAX: usize> {
estimated: usize,
cost: usize,
position: Position,
path: Vec<Edge>,
}
-impl<const M: u8, const N: u8> Node<M, N> {
+impl<const S: usize, const M: usize> Node<S, M> {
fn start(position: Position) -> Self {
Node {
position,
@@ -114,56 +147,53 @@ impl<const M: u8, const N: u8> Node<M, N> {
}
}
- fn goal(&self, position: Position) -> bool {
- self.position == position
- && self
- .path
- .iter()
- .rev()
- .take(M as usize)
- .map_windows(|[a, b]| a == b)
- .all(|b| b)
+ fn trail(&self) -> Option<[Edge; M]> {
+ self.path.last_chunk().copied()
}
- fn successors(&self, grid: &Grid) -> Vec<(Edge, Position)> {
- let Some(last) = self.path.last() else {
- return Edge::ALL
- .into_iter()
- .filter_map(|edge| self.traverse_edge(grid, edge))
- .collect::<Vec<_>>();
+ fn successors(&self, grid: &Grid) -> Vec<(Vec<Edge>, Position, usize)> {
+ let (iter, last_seq) = match self.path.last() {
+ Some(last) => (
+ last.edges(),
+ Some((
+ last,
+ self.path.iter().rev().take_while(|e| *e == last).count(),
+ )),
+ ),
+ None => (Edge::ALL.as_slice(), None),
};
- last.edges()
- .into_iter()
- .filter(|edge| {
- let seq = self.path.iter().rev().take_while(|e| *e == last).count();
- if edge == last {
- seq < N as usize
- } else {
- seq >= M as usize
+ iter.iter()
+ .flat_map(|edge| {
+ match last_seq {
+ Some((last, seq)) if last == edge => 1..=(M - seq),
+ _ => S..=M,
}
+ .map(move |step| (edge, step))
})
- .filter_map(|edge| self.traverse_edge(grid, edge))
+ .filter_map(|(edge, step)| self.traverse(*edge, step, grid))
.collect()
}
- fn traverse_edge(&self, grid: &Grid, edge: Edge) -> Option<(Edge, Position)> {
- let (row, col) = self.position;
-
- Some((
- edge,
- match edge {
- Edge::North => (row.checked_sub(1)?, col),
- Edge::East if col < grid[row].len().saturating_sub(1) => (row, col.checked_add(1)?),
- Edge::South if row < grid.len().saturating_sub(1) => (row.checked_add(1)?, col),
- Edge::West => (row, col.checked_sub(1)?),
- _ => return None,
- },
- ))
+ fn traverse(
+ &self,
+ edge: Edge,
+ step: usize,
+ grid: &Grid,
+ ) -> Option<(Vec<Edge>, Position, usize)> {
+ let mut cost = 0;
+ let mut position = self.position;
+
+ for _ in 0..step {
+ position = edge.traverse(position)?;
+ cost += grid.get(position.0)?.get(position.1)?;
+ }
+
+ Some((vec![edge; step], position, cost))
}
fn dist(&self, p2: Position) -> usize {
- self.position.0.abs_diff(p2.0) + self.position.1.abs_diff(p2.1)
+ (self.position.0.abs_diff(p2.0) + self.position.1.abs_diff(p2.1)) * S
}
}
@@ -178,14 +208,23 @@ enum Edge {
impl Edge {
const ALL: [Edge; 4] = [Edge::North, Edge::East, Edge::South, Edge::West];
- fn edges(&self) -> [Edge; 3] {
+ fn edges(&self) -> &[Edge] {
match self {
- Edge::North => [Edge::North, Edge::East, Edge::West],
- Edge::East => [Edge::North, Edge::East, Edge::South],
- Edge::South => [Edge::East, Self::South, Edge::West],
- Edge::West => [Edge::North, Edge::South, Edge::West],
+ Edge::North => &[Edge::North, Edge::East, Edge::West],
+ Edge::East => &[Edge::North, Edge::East, Edge::South],
+ Edge::South => &[Edge::East, Self::South, Edge::West],
+ Edge::West => &[Edge::North, Edge::South, Edge::West],
}
}
+
+ fn traverse(&self, (row, col): Position) -> Option<Position> {
+ Some(match self {
+ Edge::North => (row.checked_sub(1)?, col),
+ Edge::East => (row, col.checked_add(1)?),
+ Edge::South => (row.checked_add(1)?, col),
+ Edge::West => (row, col.checked_sub(1)?),
+ })
+ }
}
#[cfg(test)]