day17: problem 2 - an ugly mess but finally working

This commit is contained in:
Keenan Tims 2023-12-17 05:57:30 -08:00
parent 5efa9853ca
commit d422c9b84e
Signed by: ktims
GPG Key ID: 11230674D69038D4
3 changed files with 257 additions and 30 deletions

75
17/Cargo.lock generated
View File

@ -2,11 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "day17" name = "day17"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"petgraph", "petgraph",
"test-case",
] ]
[[package]] [[package]]
@ -46,3 +53,71 @@ dependencies = [
"fixedbitset", "fixedbitset",
"indexmap", "indexmap",
] ]
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "test-case"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-core"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "test-case-macros"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"test-case-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -7,3 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
petgraph = "0.6.4" petgraph = "0.6.4"
test-case = "3.3.1"

View File

@ -150,12 +150,18 @@ impl<'a> WalkCost<'a> {
// if cur_move.weight >= self.cost_to[cur_move.new_pos.1][cur_move.new_pos.0] { // if cur_move.weight >= self.cost_to[cur_move.new_pos.1][cur_move.new_pos.0] {
// continue; // 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 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 { 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); self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0]
} else { continue; }// visited before at lower cost } .insert((*cur_move.dir, cur_move.consecutive), cur_move.weight);
} else {
continue;
} // visited before at lower cost }
} else { } else {
self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); 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]); // println!("state at {:?}: {:?}", cur_move, self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0]);
@ -175,17 +181,21 @@ impl<'a> WalkCost<'a> {
}) })
}) })
.filter(|m| m.consecutive != 4 && *m.dir != cur_move.dir.opposite()) .filter(|m| m.consecutive != 4 && *m.dir != cur_move.dir.opposite())
.filter(|m| { .filter(|m| {
m.weight < *self.cost_from[m.new_pos.1][m.new_pos.0].get(&(*m.dir, m.consecutive)).unwrap_or(&u64::MAX) m.weight
}) < *self.cost_from[m.new_pos.1][m.new_pos.0]
.get(&(*m.dir, m.consecutive))
.unwrap_or(&u64::MAX)
})
}); // valid positions }); // valid positions
// println!("valid moves with {:?}", cur_move); // println!("valid moves with {:?}", cur_move);
for next_move in valid_moves { for next_move in valid_moves {
// println!(" {:?}", next_move); // println!(" {:?}", next_move);
unvisited_next_moves.push(next_move); unvisited_next_moves.push(next_move);
} }
} }
} }
fn shortest_path_to(&self, to: Position) -> Vec<(Position, Direction)> { fn shortest_path_to(&self, to: Position) -> Vec<(Position, Direction)> {
let mut path = Vec::new(); let mut path = Vec::new();
let mut cur_pos = to; let mut cur_pos = to;
@ -219,19 +229,140 @@ impl<'a> WalkCost<'a> {
} }
} }
impl<'a> Display for WalkCost<'a> { struct WalkCost2<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { start: Position,
// for y in 0..self.cost_to.len() { cost_from: Vec<Vec<HashMap<(Direction, usize), u64>>>,
// for cost in &self.cost_to[y] { map: &'a CityMap,
// f.write_fmt(format_args!("{:3} ", cost))?; }
// }
// f.write_str(" ")?; impl<'a> WalkCost2<'a> {
// for input in &self.map.map[y] { fn from_map(map: &'a CityMap, start: Position) -> Self {
// f.write_fmt(format_args!("{:3} ", input))?; Self {
// } map,
// writeln!(f)?; start,
// } cost_from: map
Ok(()) .map
.iter()
.map(|row| repeat(HashMap::new()).take(row.len()).collect())
.collect(),
}
}
fn compute(&mut self, to: Position) {
println!("computing to: {:?}", to);
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);
// println!("move {:?} inserted {:?}", cur_move, (*cur_move.dir, cur_move.consecutive));
} else {
continue;
} // visited before at lower cost }
} else {
// println!("move {:?} inserted {:?}", cur_move, (*cur_move.dir, cur_move.consecutive));
self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0]
.insert((*cur_move.dir, cur_move.consecutive), cur_move.weight);
}
if cur_move.new_pos == to {
// println!("reached end pos {:?} via {:?}", to, cur_move);
continue;
}
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.dir != cur_move.dir.opposite())
.filter(|m| {
if m.dir == cur_move.dir {
m.consecutive < 11
} else {
cur_move.consecutive >= 4
}
})
.filter(|m| m.new_pos != to || m.consecutive >= 4)
.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 (m, val) = self.cost_from[cur_pos.1][cur_pos.0].iter().min_by(|a, b| a.1.cmp(b.1)).unwrap();
path.push((cur_pos, m.0));
cur_pos = self.map.offset_pos(cur_pos, &m.0.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_from.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!();
}
} }
} }
@ -252,21 +383,35 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut costs = WalkCost::from_map(&map, (0, 0)); let mut costs = WalkCost::from_map(&map, (0, 0));
costs.compute(); costs.compute();
println!("{}", costs); // println!("{}", costs);
// costs.print_shortest_path((costs.cost_to[0].len() - 1, costs.cost_to.len() - 1)); // 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() *costs.cost_from[costs.cost_from.len() - 1][costs.cost_from[0].len() - 1]
.values()
.min()
.unwrap()
} }
// PROBLEM 2 solution // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0 let map = CityMap::from(input);
let mut costs = WalkCost2::from_map(&map, (0, 0));
costs.compute((map.map[0].len() - 1, map.map.len() - 1));
// println!("{}", costs);
costs.print_shortest_path((costs.cost_from[0].len() - 1, costs.cost_from.len() - 1));
*costs.cost_from[costs.cost_from.len() - 1][costs.cost_from[0].len() - 1]
.values()
.min()
.unwrap()
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::*; use crate::*;
use std::io::Cursor; use std::io::Cursor;
use test_case::test_case;
const EXAMPLE: &str = &"2413432311323 const EXAMPLE: &str = &"2413432311323
3215453535623 3215453535623
@ -280,8 +425,13 @@ mod tests {
4564679986453 4564679986453
1224686865563 1224686865563
2546548887735 2546548887735
4322674655533 4322674655533";
";
const EXAMPLE2: &str = &"111111111111
999999999991
999999999991
999999999991
999999999991";
#[test] #[test]
fn problem1_example() { fn problem1_example() {
@ -289,9 +439,10 @@ mod tests {
assert_eq!(problem1(c.lines()), 102); assert_eq!(problem1(c.lines()), 102);
} }
#[test] #[test_case(EXAMPLE, 94)]
fn problem2_example() { #[test_case(EXAMPLE2, 71)]
let c = Cursor::new(EXAMPLE); fn problem2_example(example: &str, expect: u64) {
assert_eq!(problem2(c.lines()), 0); let c = Cursor::new(example);
assert_eq!(problem2(c.lines()), expect);
} }
} }