215 lines
5.7 KiB
Rust
215 lines
5.7 KiB
Rust
advent_of_code::solution!(23);
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
struct Map {
|
|
m: [[char; 141]; 141],
|
|
width: usize,
|
|
height: usize,
|
|
}
|
|
|
|
type Coord = (usize, usize);
|
|
|
|
fn parse(input: &str) -> Map {
|
|
let mut m = [[' '; 141]; 141];
|
|
let mut width = 0;
|
|
let mut height = 0;
|
|
for (x, line) in input.lines().enumerate() {
|
|
for (y, char) in line.chars().enumerate() {
|
|
m[x][y] = char;
|
|
width = y;
|
|
}
|
|
height = x;
|
|
}
|
|
Map { m, width, height }
|
|
}
|
|
|
|
fn find_path(map: &Map, start: Coord, path: &HashSet<Coord>) -> Option<usize> {
|
|
let mut p = path.clone();
|
|
let mut cur = start.clone();
|
|
loop {
|
|
p.insert(cur);
|
|
let mut next: Vec<Coord> = vec![];
|
|
let c = map.m[cur.0][cur.1];
|
|
if c == '>' {
|
|
next.push((cur.0, cur.1 + 1));
|
|
} else if c == '<' {
|
|
next.push((cur.0, cur.1 - 1));
|
|
} else if c == 'v' {
|
|
next.push((cur.0 + 1, cur.1));
|
|
} else {
|
|
if cur.1 > 0 {
|
|
let lcoord = (cur.0, cur.1 - 1);
|
|
let l = map.m[lcoord.0][lcoord.1];
|
|
if !p.contains(&lcoord) && l != '#' && l != '>' {
|
|
next.push(lcoord);
|
|
}
|
|
}
|
|
if cur.1 < map.width - 1 {
|
|
let rcoord = (cur.0, cur.1 + 1);
|
|
let r = map.m[rcoord.0][rcoord.1];
|
|
if !p.contains(&rcoord) && r != '#' {
|
|
next.push(rcoord);
|
|
}
|
|
}
|
|
if cur.0 > 0 {
|
|
let ucoord = (cur.0 - 1, cur.1);
|
|
let u = map.m[ucoord.0][ucoord.1];
|
|
if !p.contains(&ucoord) && u != '#' && u != 'v' {
|
|
next.push(ucoord);
|
|
}
|
|
}
|
|
if cur.0 <= map.height - 1 {
|
|
let dcoord = (cur.0 + 1, cur.1);
|
|
let d = map.m[dcoord.0][dcoord.1];
|
|
if !p.contains(&dcoord) && d != '#' {
|
|
next.push(dcoord);
|
|
}
|
|
}
|
|
}
|
|
if next.is_empty() {
|
|
return None;
|
|
} else if next.len() == 1 {
|
|
cur = next[0];
|
|
} else {
|
|
return next.iter().filter_map(|k| find_path(map, *k, &p)).max();
|
|
}
|
|
if cur.0 == map.height {
|
|
break;
|
|
}
|
|
}
|
|
Some(p.len())
|
|
}
|
|
|
|
pub fn part_one(input: &str) -> Option<usize> {
|
|
let map = parse(input);
|
|
find_path(&map, (0, 1), &HashSet::new())
|
|
}
|
|
|
|
type Graph = HashMap<Coord, HashMap<Coord, usize>>;
|
|
|
|
fn to_graph(map: &Map, initial: Coord, start: Coord, r: &mut Graph) {
|
|
if !r.contains_key(&initial) {
|
|
r.insert(initial, HashMap::new());
|
|
}
|
|
let mut v: HashSet<Coord> = HashSet::new();
|
|
v.insert(initial);
|
|
let mut cur = start.clone();
|
|
let mut steps = 1;
|
|
if initial.0 == 0 && initial.1 == 1 {
|
|
steps -= 1;
|
|
}
|
|
loop {
|
|
let mut next: Vec<Coord> = vec![];
|
|
v.insert(cur);
|
|
if cur.1 > 0 {
|
|
let lcoord = (cur.0, cur.1 - 1);
|
|
let l = map.m[lcoord.0][lcoord.1];
|
|
if l != '#' {
|
|
next.push(lcoord);
|
|
}
|
|
}
|
|
if cur.1 <= map.width - 1 {
|
|
let rcoord = (cur.0, cur.1 + 1);
|
|
let r = map.m[rcoord.0][rcoord.1];
|
|
if r != '#' {
|
|
next.push(rcoord);
|
|
}
|
|
}
|
|
if cur.0 > 0 {
|
|
let ucoord = (cur.0 - 1, cur.1);
|
|
let u = map.m[ucoord.0][ucoord.1];
|
|
if u != '#' {
|
|
next.push(ucoord);
|
|
}
|
|
}
|
|
if cur.0 <= map.height - 1 {
|
|
let dcoord = (cur.0 + 1, cur.1);
|
|
let d = map.m[dcoord.0][dcoord.1];
|
|
if d != '#' {
|
|
next.push(dcoord);
|
|
}
|
|
}
|
|
if next.len() > 2 {
|
|
let parent = r.get_mut(&initial).unwrap();
|
|
if parent.contains_key(&cur) {
|
|
return;
|
|
} else {
|
|
parent.insert(cur, steps);
|
|
for x in next.iter() {
|
|
to_graph(map, cur, *x, r);
|
|
}
|
|
}
|
|
} else {
|
|
if let Some(next_spot) = next.iter().find(|n| !v.contains(*n)) {
|
|
steps += 1;
|
|
cur = *next_spot;
|
|
} else {
|
|
let parent = r.get_mut(&initial).unwrap();
|
|
if !parent.contains_key(&cur) {
|
|
parent.insert(cur, steps);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dfs(
|
|
g: &Graph,
|
|
steps: usize,
|
|
cur: Coord,
|
|
dest: Coord,
|
|
visited: &mut HashSet<Coord>,
|
|
) -> Option<usize> {
|
|
if cur == dest {
|
|
return Some(steps);
|
|
}
|
|
let neighbors = g.get(&cur).unwrap();
|
|
visited.insert(cur);
|
|
let m = neighbors
|
|
.iter()
|
|
.filter_map(|(n, dist)| {
|
|
if visited.contains(&n) {
|
|
None
|
|
} else if *n == dest {
|
|
Some(steps + dist)
|
|
} else {
|
|
dfs(g, steps + dist, *n, dest, visited)
|
|
}
|
|
})
|
|
.max();
|
|
visited.remove(&cur);
|
|
m
|
|
}
|
|
|
|
pub fn part_two(input: &str) -> Option<usize> {
|
|
let map = parse(input);
|
|
let mut g: Graph = HashMap::new();
|
|
to_graph(&map, (0, 1), (0, 1), &mut g);
|
|
dfs(
|
|
&g,
|
|
0,
|
|
(0, 1),
|
|
(map.height, map.width - 1),
|
|
&mut HashSet::new(),
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_part_one() {
|
|
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(94));
|
|
}
|
|
|
|
#[test]
|
|
fn test_part_two() {
|
|
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(154));
|
|
}
|
|
}
|