advent-of-code/src/year_2023/day_17_clumsy_crucible/part_one.rs

128 lines
3.9 KiB
Rust

use std::{collections::{HashMap, HashSet}, cmp};
use super::common::Direction;
pub fn part_one(input_lines: Vec<String>) -> String {
let mut map: Vec<Vec<i32>> = Vec::new();
for line in input_lines {
let mut row: Vec<i32> = Vec::new();
for c in line.chars() {
row.push(c.to_digit(10).unwrap() as i32);
}
map.push(row);
}
let mut cache: HashMap<(i32, i32, i32, Direction), i32> = HashMap::new();
let cost = min(
min_cost_path(&map, &mut cache, HashSet::new(), Direction::Right, 0, 0, 0),
min_cost_path(&map, &mut cache, HashSet::new(), Direction::Down, 0, 0, 0),
i32::MAX,
);
cost.to_string()
}
fn min_cost_path(
map: &Vec<Vec<i32>>,
cache: &mut HashMap<(i32, i32, i32, Direction), i32>,
mut current_path: HashSet<(i32, i32)>,
dir: Direction,
step_count: i32,
i: i32,
j: i32) -> i32 {
// println!("{}: {} {} {:?}", step_count, i, j, dir);
if i < 0 || j < 0 || i >= map.len() as i32 || j >= map[0].len() as i32 {
return i32::MAX;
}
if step_count > 2 {
return i32::MAX;
}
if current_path.contains(&(i,j)) {
return i32::MAX;
} else {
current_path.insert((i,j));
}
if i == map.len() as i32 - 1 && j == map[0].len() as i32 - 1 {
return map[i as usize][j as usize];
}
if cache.contains_key(&(i,j, step_count, dir)) {
// println!("Cache hit: i={} j={} step_count={} dir={:?} => {}", i, j, step_count, dir, cache.get(&(i,j, step_count, dir)).unwrap());
return *cache.get(&(i,j, step_count, dir)).unwrap();
}
let mut cost = 0;
if i != 0 || j != 0 {
cost = map[i as usize][j as usize];
// println!("Cost = {}", cost);
}
let res: i32;
match dir {
Direction::Up => {
res = min(
min_cost_path(map, cache, current_path.clone(), Direction::Up, step_count + 1, i - 1, j),
min_cost_path(map, cache, current_path.clone(), Direction::Left, 0, i, j - 1),
min_cost_path(map, cache, current_path.clone(), Direction::Right, 0, i, j + 1)
);
},
Direction::Down => {
res = min(
min_cost_path(map, cache, current_path.clone(), Direction::Down, step_count + 1, i + 1, j),
min_cost_path(map, cache, current_path.clone(), Direction::Left, 0, i, j - 1),
min_cost_path(map, cache, current_path.clone(), Direction::Right, 0, i, j + 1)
);
},
Direction::Left => {
res = min(
min_cost_path(map, cache, current_path.clone(), Direction::Left, step_count + 1, i, j - 1),
min_cost_path(map, cache, current_path.clone(), Direction::Up, 0, i - 1, j),
min_cost_path(map, cache, current_path.clone(), Direction::Down, 0, i + 1, j)
);
},
Direction::Right => {
res = min(
min_cost_path(map, cache, current_path.clone(), Direction::Right, step_count + 1, i, j + 1),
min_cost_path(map, cache, current_path.clone(), Direction::Up, 0, i - 1, j),
min_cost_path(map, cache, current_path.clone(), Direction::Down, 0, i + 1, j)
);
},
}
current_path.remove(&(i, j));
if let Some(new_cost) = cost.checked_add(res) {
cost = new_cost;
} else {
cost = i32::MAX;
}
if cost < i32::MAX {
cache.insert((i,j, step_count, dir), cost);
}
cost
}
fn min(a: i32, b: i32 ,c: i32) -> i32 {
cmp::min(a, cmp::min(b, c))
}
#[allow(dead_code)]
fn print_path(map: &Vec<Vec<i32>>, path: &HashSet<(i32, i32)>) {
for i in 0..map.len() {
for j in 0..map[0].len() {
if path.contains(&(i as i32, j as i32)) {
print!("#");
} else {
print!("{}", map[i][j]);
}
}
println!();
}
}