Day 15 part 1

This commit is contained in:
2021-12-15 10:37:15 -08:00
parent 2fdcf171b0
commit c16e3ad864
3 changed files with 349 additions and 0 deletions

248
2021/src/day15.rs Normal file
View File

@@ -0,0 +1,248 @@
use std::{
collections::{HashMap, HashSet, VecDeque},
convert::Infallible,
fmt::{Debug, Error, Formatter},
num::ParseIntError,
ops::{Index, IndexMut},
str::FromStr,
};
use anyhow::Result;
use aoc_runner_derive::{aoc, aoc_generator};
use thiserror::Error;
struct Image {
width: usize,
height: usize,
pixels: Vec<usize>,
}
impl Debug for Image {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
writeln!(f)?;
for y in 0..self.height {
for x in 0..self.width {
write!(f, "{}", self[(x, y)])?;
}
writeln!(f)?;
}
Ok(())
}
}
impl Index<(usize, usize)> for Image {
type Output = usize;
fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
&self.pixels[x + y * self.width]
}
}
impl IndexMut<(usize, usize)> for Image {
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
&mut self.pixels[x + y * self.width]
}
}
impl FromStr for Image {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rows: Vec<_> = s.lines().collect();
let width = rows[0].len();
let height = rows.len();
let pixels = rows
.iter()
.flat_map(|row| row.as_bytes().iter().map(|b| (b - b'0') as usize))
.collect();
Ok(Image {
width,
height,
pixels,
})
}
}
use std::{cmp::Ordering, collections::BinaryHeap};
#[derive(Copy, Clone, Eq, PartialEq)]
struct State {
cost: usize,
position: usize,
}
// The priority queue depends on `Ord`.
// Explicitly implement the trait so the queue becomes a min-heap
// instead of a max-heap.
impl Ord for State {
fn cmp(&self, other: &Self) -> Ordering {
// Notice that the we flip the ordering on costs.
// In case of a tie we compare positions - this step is necessary
// to make implementations of `PartialEq` and `Ord` consistent.
other
.cost
.cmp(&self.cost)
.then_with(|| self.position.cmp(&other.position))
}
}
// `PartialOrd` needs to be implemented as well.
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// Each node is represented as a `usize`, for a shorter implementation.
struct Edge {
node: usize,
cost: usize,
}
impl Debug for Edge {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "Edge{{node: {}, cost: {}}}", self.node, self.cost)?;
Ok(())
}
}
// From https://doc.rust-lang.org/std/collections/binary_heap/index.html
// Dijkstra's shortest path algorithm.
// Start at `start` and use `dist` to track the current shortest distance
// to each node. This implementation isn't memory-efficient as it may leave duplicate
// nodes in the queue. It also uses `usize::MAX` as a sentinel value,
// for a simpler implementation.
fn shortest_path(adj_list: &Vec<Vec<Edge>>, start: usize, goal: usize) -> Option<usize> {
// dist[node] = current shortest distance from `start` to `node`
let mut dist: Vec<_> = (0..adj_list.len()).map(|_| usize::MAX).collect();
let mut heap = BinaryHeap::new();
// We're at `start`, with a zero cost
dist[start] = 0;
heap.push(State {
cost: 0,
position: start,
});
// Examine the frontier with lower cost nodes first (min-heap)
while let Some(State { cost, position }) = heap.pop() {
// Alternatively we could have continued to find all shortest paths
if position == goal {
return Some(cost);
}
// Important as we may have already found a better way
if cost > dist[position] {
continue;
}
// For each node we can reach, see if we can find a way with
// a lower cost going through this node
for edge in &adj_list[position] {
let next = State {
cost: cost + edge.cost,
position: edge.node,
};
// If so, add it to the frontier and continue
if next.cost < dist[next.position] {
heap.push(next);
// Relaxation, we have now found a better way
dist[next.position] = next.cost;
}
}
}
// Goal not reachable
None
}
#[aoc(day15, part1)]
fn part1(input: &str) -> Result<usize> {
let im: Image = input.parse()?;
// TODO build graph by walking pixels, finding neighobrs and using their cost.
// let graph = vec![
// // Node 0
// vec![Edge { node: 2, cost: 10 },
// Edge { node: 1, cost: 1 }],
// // Node 1
// vec![Edge { node: 3, cost: 2 }],
// ...
let idx = |x, y| y * im.width + x;
let mut graph: Vec<_> = Vec::new();
for y in 0..im.height {
for x in 0..im.width {
let mut edges = Vec::new();
if x > 0 {
edges.push(Edge {
node: idx(x - 1, y),
cost: im[(x - 1, y)],
});
}
if x < im.width - 1 {
edges.push(Edge {
node: idx(x + 1, y),
cost: im[(x + 1, y)],
});
}
if y > 0 {
edges.push(Edge {
node: idx(x, y - 1),
cost: im[(x, y - 1)],
});
}
if y < im.height - 1 {
edges.push(Edge {
node: idx(x, y + 1),
cost: im[(x, y + 1)],
});
}
graph.push(edges);
}
}
Ok(shortest_path(&graph, 0, im.pixels.len() - 1).unwrap())
}
/*
#[aoc(day15, part2)]
fn part2(input: &str) -> Result<usize> {
todo!("part2");
Ok(0)
}
*/
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part1() -> Result<()> {
let input = r#"
1163751742
1381373672
2136511328
3694931569
7463417111
1319128137
1359912421
3125421639
1293138521
2311944581
"#
.trim();
assert_eq!(part1(input)?, 40);
Ok(())
}
/*
#[test]
fn test_part2()->Result<()> {
let input = r#"
"#
.trim();
assert_eq!(part2(input)?, usize::MAX);
Ok(())
}
*/
}

View File

@@ -4,6 +4,7 @@ pub mod day11;
pub mod day12;
pub mod day13;
pub mod day14;
pub mod day15;
pub mod day2;
pub mod day3;
pub mod day4;