Solve 16
This commit is contained in:
parent
20be794e88
commit
6c3b0cabf1
1 changed files with 106 additions and 186 deletions
292
src/bin/16.rs
292
src/bin/16.rs
|
@ -1,7 +1,9 @@
|
||||||
use aoc::*;
|
use aoc::*;
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::{fmt, iter};
|
use std::iter;
|
||||||
|
|
||||||
const INPUT: &str = include_str!("../../input/16");
|
const INPUT: &str = include_str!("../../input/16");
|
||||||
|
|
||||||
|
@ -9,181 +11,121 @@ fn main() {
|
||||||
assert_example!(part1, "16-test", 7036);
|
assert_example!(part1, "16-test", 7036);
|
||||||
println!("Part 1: {}", part1(INPUT));
|
println!("Part 1: {}", part1(INPUT));
|
||||||
assert_example!(part2, "16-test", 45);
|
assert_example!(part2, "16-test", 45);
|
||||||
// TODO Make part 2 terminate
|
|
||||||
println!("Part 2: {}", part2(INPUT));
|
println!("Part 2: {}", part2(INPUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn part1(input: &str) -> usize {
|
fn part1(input: &str) -> usize {
|
||||||
let mut maze = Maze::parse(input);
|
let maze = Maze::parse(input);
|
||||||
maze.solve();
|
maze.dijsktra().lowest_score(maze.end)
|
||||||
maze.best_score()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn part2(input: &str) -> usize {
|
fn part2(input: &str) -> usize {
|
||||||
let mut maze = Maze::parse(input);
|
Maze::parse(input).good_seats()
|
||||||
maze.solve();
|
|
||||||
maze.good_seats()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Tile {
|
struct Tile {
|
||||||
pos: IVec2,
|
|
||||||
visited: bool,
|
|
||||||
score: usize,
|
score: usize,
|
||||||
direction: Direction,
|
visited: bool,
|
||||||
}
|
previous: Vec<(IVec2, Direction)>,
|
||||||
|
|
||||||
impl Tile {
|
|
||||||
fn new(pos: IVec2) -> Self {
|
|
||||||
Self {
|
|
||||||
pos,
|
|
||||||
visited: false,
|
|
||||||
score: usize::MAX,
|
|
||||||
direction: Direction::East,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Maze {
|
struct Maze {
|
||||||
start: IVec2,
|
start: IVec2,
|
||||||
end: IVec2,
|
end: IVec2,
|
||||||
walls: HashSet<IVec2>,
|
tiles: HashSet<IVec2>,
|
||||||
tiles: HashMap<IVec2, Tile>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Maze {
|
impl Maze {
|
||||||
fn solve(&mut self) {
|
fn good_seats(&self) -> usize {
|
||||||
|
let dijkstra = self.dijsktra();
|
||||||
|
let endings = dijkstra
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|((pos, _), _)| *pos == self.end)
|
||||||
|
.min_set_by_key(|(_, tile)| tile.score);
|
||||||
|
endings
|
||||||
|
.into_iter()
|
||||||
|
.map(|(end, _)| dijkstra.good_seats(*end))
|
||||||
|
.fold(HashSet::new(), |mut a, b| {
|
||||||
|
a.extend(b);
|
||||||
|
a
|
||||||
|
})
|
||||||
|
.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijsktra(&self) -> Dijkstra {
|
||||||
|
let mut maze: HashMap<(IVec2, Direction), Tile> = self
|
||||||
|
.tiles
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.cartesian_product(Direction::ALL)
|
||||||
|
.zip(iter::repeat(Tile {
|
||||||
|
score: usize::MAX,
|
||||||
|
visited: false,
|
||||||
|
previous: Vec::new(),
|
||||||
|
}))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
maze.insert(
|
||||||
|
(self.start, Direction::East),
|
||||||
|
Tile {
|
||||||
|
score: 0,
|
||||||
|
visited: false,
|
||||||
|
previous: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next = self
|
// Pick unvisited with minimal distance
|
||||||
.tiles
|
let next = maze
|
||||||
.values()
|
.iter()
|
||||||
.filter(|t| !t.visited)
|
.filter(|(_, tile)| !tile.visited)
|
||||||
.min_by_key(|t| t.score);
|
.min_by_key(|(_, tile)| tile.score)
|
||||||
let Some(next) = next.cloned() else {
|
.map(|(&k, v)| (k, v.clone()));
|
||||||
|
let Some(((pos, dir), current)) = next else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.tiles.insert(
|
// Mark visited
|
||||||
next.pos,
|
maze.insert(
|
||||||
|
(pos, dir),
|
||||||
Tile {
|
Tile {
|
||||||
visited: true,
|
visited: true,
|
||||||
..next
|
..current
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
for dir in Direction::ALL {
|
// Update neighbors
|
||||||
let neighbor = next.pos + dir.vec();
|
let update_neighbor = |add: usize| {
|
||||||
let Some(neighbor) = self.tiles.get_mut(&neighbor) else {
|
let new_score = current.score + add;
|
||||||
continue;
|
move |neighbor: &mut Tile| match new_score.cmp(&neighbor.score) {
|
||||||
};
|
Ordering::Less => {
|
||||||
if neighbor.visited {
|
neighbor.score = new_score;
|
||||||
continue;
|
neighbor.previous = vec![(pos, dir)];
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
neighbor.previous.push((pos, dir));
|
||||||
|
}
|
||||||
|
Ordering::Greater => {}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let new_score = if dir == next.direction {
|
maze.entry((pos + dir.vec(), dir))
|
||||||
next.score + 1
|
.and_modify(update_neighbor(1));
|
||||||
} else {
|
maze.entry((pos, dir.rotate_cw()))
|
||||||
next.score + 1001
|
.and_modify(update_neighbor(1000));
|
||||||
};
|
maze.entry((pos, dir.rotate_ccw()))
|
||||||
|
.and_modify(update_neighbor(1000));
|
||||||
if new_score < neighbor.score {
|
|
||||||
neighbor.score = new_score;
|
|
||||||
neighbor.direction = dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// println!("===");
|
Dijkstra(maze)
|
||||||
// let size = self.tiles.iter().fold(IVec2::ZERO, |a, b| a.max(*b.0));
|
|
||||||
// for y in 1..=size.y {
|
|
||||||
// for x in 1..=size.x {
|
|
||||||
// let pos = IVec2::new(x, y);
|
|
||||||
// let tile = self.tiles.get(&pos).map(|t| t.score).unwrap_or(0);
|
|
||||||
// print!("{tile:>6}");
|
|
||||||
// }
|
|
||||||
// println!();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_score(&self) -> usize {
|
|
||||||
self.tiles.get(&self.end).unwrap().score
|
|
||||||
}
|
|
||||||
|
|
||||||
fn good_seats(&self) -> usize {
|
|
||||||
self.good_seats_rec(
|
|
||||||
self.best_score(),
|
|
||||||
0,
|
|
||||||
HashSet::new(),
|
|
||||||
Direction::East,
|
|
||||||
self.start,
|
|
||||||
)
|
|
||||||
.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn good_seats_rec(
|
|
||||||
&self,
|
|
||||||
target_score: usize,
|
|
||||||
score: usize,
|
|
||||||
mut visited: HashSet<IVec2>,
|
|
||||||
dir: Direction,
|
|
||||||
pos: IVec2,
|
|
||||||
) -> HashSet<IVec2> {
|
|
||||||
if score > target_score {
|
|
||||||
return HashSet::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.tiles.contains_key(&pos) {
|
|
||||||
return HashSet::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
if visited.contains(&pos) {
|
|
||||||
return HashSet::new();
|
|
||||||
}
|
|
||||||
visited.insert(pos);
|
|
||||||
|
|
||||||
if score == target_score && pos == self.end {
|
|
||||||
return visited;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = HashSet::new();
|
|
||||||
{
|
|
||||||
result.extend(self.good_seats_rec(
|
|
||||||
target_score,
|
|
||||||
score + 1,
|
|
||||||
visited.clone(),
|
|
||||||
dir,
|
|
||||||
pos + dir.vec(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let new_dir = dir.rotate_cw();
|
|
||||||
result.extend(self.good_seats_rec(
|
|
||||||
target_score,
|
|
||||||
score + 1001,
|
|
||||||
visited.clone(),
|
|
||||||
new_dir,
|
|
||||||
pos + new_dir.vec(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let new_dir = dir.rotate_ccw();
|
|
||||||
result.extend(self.good_seats_rec(
|
|
||||||
target_score,
|
|
||||||
score + 1001,
|
|
||||||
visited.clone(),
|
|
||||||
new_dir,
|
|
||||||
pos + new_dir.vec(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(input: &str) -> Self {
|
fn parse(input: &str) -> Self {
|
||||||
let mut start = IVec2::ZERO;
|
let mut start = IVec2::ZERO;
|
||||||
let mut end = IVec2::ZERO;
|
let mut end = IVec2::ZERO;
|
||||||
let mut tiles = HashMap::new();
|
let mut tiles = HashSet::new();
|
||||||
let mut walls = HashSet::new();
|
|
||||||
|
|
||||||
let input = input
|
let input = input
|
||||||
.lines()
|
.lines()
|
||||||
|
@ -194,60 +136,49 @@ impl Maze {
|
||||||
match c {
|
match c {
|
||||||
'S' => {
|
'S' => {
|
||||||
start = pos;
|
start = pos;
|
||||||
tiles.insert(
|
tiles.insert(pos);
|
||||||
pos,
|
|
||||||
Tile {
|
|
||||||
pos,
|
|
||||||
visited: false,
|
|
||||||
score: 0,
|
|
||||||
direction: Direction::East,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
'E' => {
|
'E' => {
|
||||||
end = pos;
|
end = pos;
|
||||||
tiles.insert(pos, Tile::new(pos));
|
tiles.insert(pos);
|
||||||
}
|
|
||||||
'#' => {
|
|
||||||
walls.insert(pos);
|
|
||||||
}
|
}
|
||||||
|
'#' => {}
|
||||||
'.' => {
|
'.' => {
|
||||||
tiles.insert(pos, Tile::new(pos));
|
tiles.insert(pos);
|
||||||
}
|
}
|
||||||
other => panic!("unknown tile: '{other}'"),
|
other => panic!("unknown tile: '{other}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self { start, end, tiles }
|
||||||
start,
|
|
||||||
end,
|
|
||||||
walls,
|
|
||||||
tiles,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Maze {
|
struct Dijkstra(HashMap<(IVec2, Direction), Tile>);
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let size = self.walls.iter().fold(IVec2::ZERO, |a, b| a.max(*b));
|
impl Dijkstra {
|
||||||
for y in 0..=size.y {
|
fn lowest_score(&self, at: IVec2) -> usize {
|
||||||
for x in 0..=size.x {
|
Direction::ALL
|
||||||
let pos = IVec2::new(x, y);
|
.into_iter()
|
||||||
if self.walls.contains(&pos) {
|
.map(|dir| self.0.get(&(at, dir)).unwrap().score)
|
||||||
write!(f, "█")?;
|
.min()
|
||||||
} else if let Some(t) = self.tiles.get(&pos) {
|
.unwrap()
|
||||||
write!(f, "{}", t.direction)?;
|
}
|
||||||
} else {
|
|
||||||
write!(f, "?")?;
|
fn good_seats(&self, current: (IVec2, Direction)) -> HashSet<IVec2> {
|
||||||
}
|
let current_tile = self.0.get(¤t).unwrap();
|
||||||
}
|
|
||||||
writeln!(f)?;
|
let mut seats = HashSet::from([current.0]);
|
||||||
|
|
||||||
|
for &prev in ¤t_tile.previous {
|
||||||
|
seats.extend(self.good_seats(prev));
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
seats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
enum Direction {
|
enum Direction {
|
||||||
North,
|
North,
|
||||||
East,
|
East,
|
||||||
|
@ -290,14 +221,3 @@ impl Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Direction {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::North => write!(f, "^"),
|
|
||||||
Self::East => write!(f, ">"),
|
|
||||||
Self::South => write!(f, "v"),
|
|
||||||
Self::West => write!(f, "<"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue