day(23): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
896a728db6
commit
9a990dc83a
2 changed files with 203 additions and 0 deletions
23
samples/day23.txt
Normal file
23
samples/day23.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#.#####################
|
||||||
|
#.......#########...###
|
||||||
|
#######.#########.#.###
|
||||||
|
###.....#.>.>.###.#.###
|
||||||
|
###v#####.#v#.###.#.###
|
||||||
|
###.>...#.#.#.....#...#
|
||||||
|
###v###.#.#.#########.#
|
||||||
|
###...#.#.#.......#...#
|
||||||
|
#####.#.#.#######.#.###
|
||||||
|
#.....#.#.#.......#...#
|
||||||
|
#.#####.#.#.#########v#
|
||||||
|
#.#...#...#...###...>.#
|
||||||
|
#.#.#v#######v###.###v#
|
||||||
|
#...#.>.#...>.>.#.###.#
|
||||||
|
#####v#.#.###v#.#.###.#
|
||||||
|
#.....#...#...#.#.#...#
|
||||||
|
#.#########.###.#.#.###
|
||||||
|
#...###...#...#...#.###
|
||||||
|
###.###.#.###v#####v###
|
||||||
|
#...#...#.#.>.>.#.>.###
|
||||||
|
#.###.###.#.###.#.#v###
|
||||||
|
#.....###...###...#...#
|
||||||
|
#####################.#
|
180
src/bin/day23.rs
Normal file
180
src/bin/day23.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
|
||||||
|
use aoc_2023::*;
|
||||||
|
use itertools::iproduct;
|
||||||
|
|
||||||
|
type Output1 = isize;
|
||||||
|
type Output2 = Output1;
|
||||||
|
|
||||||
|
type Coord = Vector2D<isize>;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LEFT: Coord = Coord::new(-1, 0);
|
||||||
|
static ref RIGHT: Coord = Coord::new(1, 0);
|
||||||
|
static ref UP: Coord = Coord::new(0, -1);
|
||||||
|
static ref DOWN: Coord = Coord::new(0, 1);
|
||||||
|
static ref DIRECTIONS: Vec<Coord> = vec![*LEFT, *RIGHT, *UP, *DOWN];
|
||||||
|
static ref EXITS: HashMap<char, Vec<Coord>> = HashMap::from([
|
||||||
|
('<', vec![*LEFT]),
|
||||||
|
('>', vec![*RIGHT]),
|
||||||
|
('^', vec![*UP]),
|
||||||
|
('v', vec![*DOWN]),
|
||||||
|
('.', DIRECTIONS.clone()),
|
||||||
|
('#', vec![]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Edge {
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
distance: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Graph {
|
||||||
|
nodes: Vec<usize>,
|
||||||
|
edges: Vec<Edge>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Day23 {
|
||||||
|
graph: Graph,
|
||||||
|
graph_no_slopes: Graph,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char_identity(c: char) -> char {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
fn slope_remover(c: char) -> char {
|
||||||
|
if ">v<^".contains(c) {
|
||||||
|
'.'
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Day23 {
|
||||||
|
fn make_graph(lines: &[String], transform: &dyn Fn(char) -> char) -> Graph {
|
||||||
|
let map: HashMap<Coord, char> = lines
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(y, row)| {
|
||||||
|
row.chars()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(x, c)| (Coord::new(x as isize, y as isize), transform(c)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let is_free = |p| map.get(&p).is_some_and(|&c| c != '#');
|
||||||
|
let is_road = |p| is_free(p) && DIRECTIONS.iter().filter(|&&d| is_free(p + d)).count() == 2;
|
||||||
|
let distance = |src, dst| {
|
||||||
|
let mut q: VecDeque<(Vector2D<isize>, usize)> = VecDeque::new();
|
||||||
|
q.push_back((src, 0));
|
||||||
|
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
visited.insert(src);
|
||||||
|
|
||||||
|
while let Some((u, d)) = q.pop_front() {
|
||||||
|
for direction in &EXITS[&map[&u]] {
|
||||||
|
let v = u + *direction;
|
||||||
|
|
||||||
|
if v == dst {
|
||||||
|
return Some(d + 1);
|
||||||
|
} else if is_road(v) && !visited.contains(&v) {
|
||||||
|
visited.insert(v);
|
||||||
|
q.push_back((v, d + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let nodes_coords = map
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.filter(|p| is_free(*p) && !is_road(*p))
|
||||||
|
.sorted_by_key(|p| (p.y(), p.x()))
|
||||||
|
.collect_vec();
|
||||||
|
let nodes = (0..nodes_coords.len()).map(|i| 1_usize << i).collect_vec();
|
||||||
|
|
||||||
|
let edges = iproduct!(0..nodes_coords.len(), 0..nodes_coords.len())
|
||||||
|
.filter(|(i, j)| i != j)
|
||||||
|
.filter_map(|(i, j)| {
|
||||||
|
distance(nodes_coords[i], nodes_coords[j]).map(|d| Edge {
|
||||||
|
start: nodes[i],
|
||||||
|
end: nodes[j],
|
||||||
|
distance: d,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
Graph { nodes, edges }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve(g: &Graph) -> isize {
|
||||||
|
let start = *g.nodes.first().unwrap();
|
||||||
|
let goal = *g.nodes.last().unwrap();
|
||||||
|
|
||||||
|
fn longest_path(
|
||||||
|
cache: &mut HashMap<(usize, usize), isize>,
|
||||||
|
edges: &[Edge],
|
||||||
|
goal: usize,
|
||||||
|
u: usize,
|
||||||
|
visited: usize,
|
||||||
|
) -> isize {
|
||||||
|
if u == goal {
|
||||||
|
return 0;
|
||||||
|
} else if (visited & u) != 0 {
|
||||||
|
return i32::MIN as isize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = (u, visited);
|
||||||
|
let missing = !cache.contains_key(&key);
|
||||||
|
let distance = missing.then(|| {
|
||||||
|
edges
|
||||||
|
.iter()
|
||||||
|
.filter_map(|e| {
|
||||||
|
(e.start == u).then(|| {
|
||||||
|
e.distance as isize
|
||||||
|
+ longest_path(cache, edges, goal, e.end, visited | u)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
*cache.entry(key).or_insert_with(|| distance.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cache: HashMap<(usize, usize), isize> = HashMap::new();
|
||||||
|
longest_path(&mut cache, &g.edges, goal, start, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Solution<Output1, Output2> for Day23 {
|
||||||
|
fn new<P: AsRef<Path>>(pathname: P) -> Self {
|
||||||
|
let lines: Vec<String> = file_to_lines(pathname);
|
||||||
|
|
||||||
|
let graph = Self::make_graph(&lines, &char_identity);
|
||||||
|
let graph_no_slopes = Self::make_graph(&lines, &slope_remover);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
graph,
|
||||||
|
graph_no_slopes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_1(&mut self) -> Output1 {
|
||||||
|
Self::solve(&self.graph)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_2(&mut self) -> Output2 {
|
||||||
|
Self::solve(&self.graph_no_slopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
Day23::main()
|
||||||
|
}
|
||||||
|
|
||||||
|
test_sample!(day_23, Day23, 94, 154);
|
Loading…
Reference in a new issue