day17: problem 1 solution - a messy one!

This commit is contained in:
2023-12-17 04:38:35 -08:00
parent da25b73eca
commit 5efa9853ca
4 changed files with 495 additions and 0 deletions

297
17/src/main.rs Normal file
View File

@ -0,0 +1,297 @@
use std::collections::hash_map::RandomState;
use std::collections::{BinaryHeap, HashMap};
use std::fmt::{Display, Write};
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::iter::repeat;
use std::time::Instant;
use petgraph::algo::dijkstra;
use petgraph::prelude::*;
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
let start = Instant::now();
let ans1 = problem1(get_input());
let duration = start.elapsed();
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
let start = Instant::now();
let ans2 = problem2(get_input());
let duration = start.elapsed();
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
}
// PARSE
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
enum Direction {
Left,
Right,
Up,
Down,
}
impl Direction {
const fn all() -> &'static [Self; 4] {
&[
Direction::Left,
Direction::Right,
Direction::Up,
Direction::Down,
]
}
const fn opposite(&self) -> Self {
match self {
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
}
}
}
struct CityMap {
map: Vec<Vec<u64>>,
}
impl CityMap {
fn offset_pos(&self, pos: (usize, usize), dir: &Direction) -> Option<(usize, usize)> {
match dir {
Direction::Left if pos.0 > 0 => Some((pos.0 - 1, pos.1)),
Direction::Right if pos.0 < self.map[0].len() - 1 => Some((pos.0 + 1, pos.1)),
Direction::Up if pos.1 > 0 => Some((pos.0, pos.1 - 1)),
Direction::Down if pos.1 < self.map.len() - 1 => Some((pos.0, pos.1 + 1)),
_ => None,
}
}
}
type Position = (usize, usize);
struct WalkCost<'a> {
start: Position,
cost_from: Vec<Vec<HashMap<(Direction, usize), u64>>>,
dir_to: HashMap<Position, Direction>,
map: &'a CityMap,
}
#[derive(Debug)]
struct Move {
new_pos: Position,
dir: &'static Direction,
consecutive: usize,
weight: u64,
}
impl PartialEq for Move {
fn eq(&self, other: &Self) -> bool {
self.weight == other.weight
}
}
impl Eq for Move {}
impl PartialOrd for Move {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Move {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
std::cmp::Reverse(self.weight).cmp(&std::cmp::Reverse(other.weight))
}
}
impl<'a> WalkCost<'a> {
fn from_map(map: &'a CityMap, start: Position) -> Self {
Self {
map,
start,
cost_from: map
.map
.iter()
.map(|row| repeat(HashMap::new()).take(row.len()).collect())
.collect(),
dir_to: HashMap::new(),
}
}
fn compute(&mut self) {
let mut unvisited_next_moves: BinaryHeap<Move> = BinaryHeap::new();
let valid_start_moves: Vec<Move> = Direction::all()
.iter()
.filter_map(|dir| {
self.map.offset_pos(self.start, dir).and_then(|pos| {
Some(Move {
new_pos: pos,
dir,
consecutive: 1,
weight: self.map.map[pos.1][pos.0],
})
})
}) // valid positions
.collect();
for m in valid_start_moves {
unvisited_next_moves.push(m);
}
while let Some(cur_move) = unvisited_next_moves.pop() {
// we've been here already at a lower cost
// if cur_move.weight >= self.cost_to[cur_move.new_pos.1][cur_move.new_pos.0] {
// continue;
// }
if let Some(last_weight) = self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].get(&(*cur_move.dir, cur_move.consecutive)) {
if cur_move.weight < *last_weight {
self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].insert((*cur_move.dir, cur_move.consecutive), cur_move.weight);
} else { continue; }// visited before at lower cost }
} else {
self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].insert((*cur_move.dir, cur_move.consecutive), cur_move.weight);
}
// println!("state at {:?}: {:?}", cur_move, self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0]);
let valid_moves = Direction::all().iter().filter_map(|dir| {
self.map
.offset_pos(cur_move.new_pos, dir)
.and_then(|new_pos| {
Some(Move {
new_pos,
dir,
consecutive: if cur_move.dir == dir {
cur_move.consecutive + 1
} else {
1
},
weight: cur_move.weight + self.map.map[new_pos.1][new_pos.0],
})
})
.filter(|m| m.consecutive != 4 && *m.dir != cur_move.dir.opposite())
.filter(|m| {
m.weight < *self.cost_from[m.new_pos.1][m.new_pos.0].get(&(*m.dir, m.consecutive)).unwrap_or(&u64::MAX)
})
}); // valid positions
// println!("valid moves with {:?}", cur_move);
for next_move in valid_moves {
// println!(" {:?}", next_move);
unvisited_next_moves.push(next_move);
}
}
}
fn shortest_path_to(&self, to: Position) -> Vec<(Position, Direction)> {
let mut path = Vec::new();
let mut cur_pos = to;
// start at the end, walk backwards
while cur_pos != self.start {
let dir = self.dir_to.get(&cur_pos).unwrap();
path.push((cur_pos, *dir));
cur_pos = self.map.offset_pos(cur_pos, &dir.opposite()).unwrap();
}
path
}
fn print_shortest_path(&self, to: Position) {
let path = self.shortest_path_to(to);
let map: HashMap<_, _, RandomState> = HashMap::from_iter(path.into_iter());
// for y in 0..self.cost_to.len() {
// for x in 0..self.map.map[y].len() {
// if let Some(to_dir) = map.get(&(x, y)) {
// let c = match to_dir {
// Direction::Left => '<',
// Direction::Right => '>',
// Direction::Up => '^',
// Direction::Down => 'v',
// };
// print!("{}", c);
// } else {
// print!("{}", self.map.map[y][x]);
// }
// }
// println!();
// }
}
}
impl<'a> Display for WalkCost<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// for y in 0..self.cost_to.len() {
// for cost in &self.cost_to[y] {
// f.write_fmt(format_args!("{:3} ", cost))?;
// }
// f.write_str(" ")?;
// for input in &self.map.map[y] {
// f.write_fmt(format_args!("{:3} ", input))?;
// }
// writeln!(f)?;
// }
Ok(())
}
}
impl<T: BufRead> From<Lines<T>> for CityMap {
fn from(lines: Lines<T>) -> Self {
Self {
map: lines
.map(|l| l.unwrap().chars().map(|c| c as u64 - '0' as u64).collect())
.collect(),
}
}
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let map = CityMap::from(input);
let mut costs = WalkCost::from_map(&map, (0, 0));
costs.compute();
println!("{}", costs);
// costs.print_shortest_path((costs.cost_to[0].len() - 1, costs.cost_to.len() - 1));
*costs.cost_from[costs.cost_from.len()-1][costs.cost_from[0].len() - 1].values().min().unwrap()
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &"2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533
";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 102);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0);
}
}