This commit is contained in:
sepia 2024-12-15 14:57:07 -06:00
parent 290789d5f5
commit ea1bbcc089
4 changed files with 246 additions and 6 deletions

172
src/day15.rs Normal file
View File

@ -0,0 +1,172 @@
use crate::util::maps::*;
use itertools::Itertools;
use pest::{
iterators::{Pair, Pairs},
Parser,
};
use pest_derive::Parser;
use std::fmt::Display;
const DOUBLE_WIDE: bool = true;
pub fn day15() {
let (mut map, directions) = parse(&crate::input(15));
if DOUBLE_WIDE {
map = double_wide_map(map);
}
let (mut x, mut y, _) = map
.enumerate()
.find(|(_, _, &t)| t == Terrain::Robot)
.unwrap();
for direction in directions {
if let Some(new_map) = move_on_map(&map, x, y, direction) {
map = new_map;
(x, y) = map.travel(x, y, direction).unwrap();
}
}
let sum_gps: usize = map
.enumerate()
.filter(|(_, _, &t)| {
t == Terrain::Block(None) || t == Terrain::Block(Some(Direction::East))
})
.map(|(x, y, _)| y * 100 + x)
.sum();
println!("{}", map);
println!("Sum of GPS Coordinates: {}", sum_gps);
}
fn move_on_map(
map: &Map<Terrain>,
x: usize,
y: usize,
direction: Direction,
) -> Option<Map<Terrain>> {
let cascading_move = || {
let (nx, ny) = map.travel(x, y, direction)?;
let mut downstream = move_on_map(map, nx, ny, direction)?;
downstream.set(nx, ny, map.get(x, y).unwrap());
downstream.set(x, y, Terrain::Empty);
Some(downstream)
};
match map.get(x, y).unwrap() {
Terrain::Wall => None,
Terrain::Empty => Some(map.clone()),
Terrain::Block(None) => (cascading_move)(),
Terrain::Block(Some(d)) if d.is_parallel(direction) => (cascading_move)(),
Terrain::Robot => (cascading_move)(),
Terrain::Block(Some(Direction::West)) => {
let (partner_x, partner_y) = map.travel(x, y, Direction::West).unwrap();
move_on_map(map, partner_x, partner_y, direction)
}
Terrain::Block(Some(Direction::East)) => {
let (partner_x, partner_y) = map.travel(x, y, Direction::East).unwrap();
let mut downstream = (cascading_move)()?;
let (partner_nx, partner_ny) = map.travel(partner_x, partner_y, direction).unwrap();
downstream = move_on_map(&downstream, partner_nx, partner_ny, direction)?;
downstream.set(
partner_nx,
partner_ny,
map.get(partner_x, partner_y).unwrap(),
);
downstream.set(partner_x, partner_y, Terrain::Empty);
Some(downstream)
}
Terrain::Block(Some(_)) => unimplemented!("North/South blocks are not implemented."),
}
}
fn double_wide_map(map: Map<Terrain>) -> Map<Terrain> {
let new_map = map
.iter_rows()
.map(|row| {
row.iter()
.flat_map(|&t| match t {
Terrain::Wall => vec![Terrain::Wall; 2],
Terrain::Empty => vec![Terrain::Empty; 2],
Terrain::Block(None) => vec![
Terrain::Block(Some(Direction::East)),
Terrain::Block(Some(Direction::West)),
],
Terrain::Robot => vec![Terrain::Robot, Terrain::Empty],
Terrain::Block(Some(_)) => unreachable!(),
})
.collect()
})
.collect();
Map::from_2d_vec(new_map)
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum Terrain {
Wall,
Empty,
Block(Option<Direction>),
Robot,
}
impl Display for Terrain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Terrain::Robot => "@",
Terrain::Wall => "#",
Terrain::Empty => ".",
Terrain::Block(Some(Direction::East)) => "[",
Terrain::Block(Some(Direction::West)) => "]",
Terrain::Block(_) => "O",
};
f.write_str(s)
}
}
#[derive(Parser)]
#[grammar = "grammars/day15.pest"]
struct Day15Parser {}
fn parse(input: &str) -> (Map<Terrain>, Vec<Direction>) {
let (map, directions) = Day15Parser::parse(Rule::input, input)
.unwrap()
.nth(0)
.unwrap()
.into_inner()
.map(Pair::<'_, Rule>::into_inner)
.collect_tuple()
.unwrap();
(parse_map(map), parse_directions(directions))
}
fn parse_map(input: Pairs<'_, Rule>) -> Map<Terrain> {
let map_vec = input
.map(Pair::<'_, Rule>::into_inner)
.map(|row| {
row.map(|terrain| match terrain.as_rule() {
Rule::wall => Terrain::Wall,
Rule::empty => Terrain::Empty,
Rule::block => Terrain::Block(None),
Rule::robot => Terrain::Robot,
_ => unreachable!(),
})
.collect()
})
.collect();
Map::from_2d_vec(map_vec)
}
fn parse_directions(input: Pairs<'_, Rule>) -> Vec<Direction> {
input
.map(|d| match d.as_rule() {
Rule::north => Direction::North,
Rule::west => Direction::West,
Rule::east => Direction::East,
Rule::south => Direction::South,
_ => unreachable!(),
})
.collect()
}

19
src/grammars/day15.pest Normal file
View File

@ -0,0 +1,19 @@
_WHITESPACE = _{ " " }
NEWLINE = _{ "\n" }
wall = @{ "#" }
empty = @{ "." }
block = @{ "O" }
robot = @{ "@" }
space = _{ wall | empty | block | robot }
row = { space+ ~ NEWLINE+ }
map = { row+ }
north = @{ "^" }
west = @{ "<" }
east = @{ ">" }
south = @{ "v" }
direction_list = { ((north | west | east | south) ~ NEWLINE*)+ }
eoi = _{ !ANY }
input = { SOI ~ map ~ direction_list ~ eoi }

View File

@ -26,10 +26,12 @@ mod day11;
mod day12; mod day12;
#[allow(dead_code)] #[allow(dead_code)]
mod day13; mod day13;
#[allow(dead_code)]
mod day14; mod day14;
mod day15;
fn main() { fn main() {
day14::day14(); day15::day15();
} }
pub fn input(day: u8) -> String { pub fn input(day: u8) -> String {

View File

@ -1,6 +1,8 @@
use std::fmt::Display;
use itertools::Itertools; use itertools::Itertools;
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Direction { pub enum Direction {
North, North,
South, South,
@ -12,12 +14,25 @@ impl Direction {
pub fn all_variants() -> Vec<Self> { pub fn all_variants() -> Vec<Self> {
vec![Self::North, Self::East, Self::West, Self::South] vec![Self::North, Self::East, Self::West, Self::South]
} }
pub fn opposite(self) -> Self {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::West => Self::East,
Self::South => Self::North,
}
}
pub fn is_parallel(self, other: Self) -> bool {
self == other || self == other.opposite()
}
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Map<T> pub struct Map<T>
where where
T: Copy, T: Copy + Display,
{ {
pub map: Vec<Vec<T>>, pub map: Vec<Vec<T>>,
} }
@ -36,7 +51,7 @@ impl Map<char> {
impl<T> Map<T> impl<T> Map<T>
where where
T: Copy, T: Copy + Display,
{ {
pub fn from_2d_vec(map: Vec<Vec<T>>) -> Self { pub fn from_2d_vec(map: Vec<Vec<T>>) -> Self {
Self { map } Self { map }
@ -69,13 +84,23 @@ where
} }
} }
pub fn get(&self, x: usize, y: usize) -> Option<T> {
Some(*self.map.get(y)?.get(x)?)
}
pub fn travel_get(&self, x: usize, y: usize, direction: Direction) -> Option<T> { pub fn travel_get(&self, x: usize, y: usize, direction: Direction) -> Option<T> {
let (nx, ny) = self.travel(x, y, direction)?; let (nx, ny) = self.travel(x, y, direction)?;
self.get(nx, ny) self.get(nx, ny)
} }
pub fn get(&self, x: usize, y: usize) -> Option<T> { pub fn travel_and_get(
Some(*self.map.get(y)?.get(x)?) &self,
x: usize,
y: usize,
direction: Direction,
) -> Option<(usize, usize, T)> {
let (nx, ny) = self.travel(x, y, direction)?;
Some((nx, ny, self.get(nx, ny)?))
} }
pub fn set(&mut self, x: usize, y: usize, value: T) { pub fn set(&mut self, x: usize, y: usize, value: T) {
@ -98,6 +123,10 @@ where
self.map.iter().flat_map(|row| row.iter()) self.map.iter().flat_map(|row| row.iter())
} }
pub fn iter_rows(&self) -> impl Iterator<Item = &Vec<T>> {
self.map.iter()
}
pub fn coordinates(&self) -> impl Iterator<Item = (usize, usize)> { pub fn coordinates(&self) -> impl Iterator<Item = (usize, usize)> {
(0..self.width()).cartesian_product(0..self.height()) (0..self.width()).cartesian_product(0..self.height())
} }
@ -106,3 +135,21 @@ where
self.coordinates().map(|(x, y)| (x, y, &self.map[y][x])) self.coordinates().map(|(x, y)| (x, y, &self.map[y][x]))
} }
} }
impl<T> Display for Map<T>
where
T: Copy + Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut final_string = "".to_string();
for row in self.map.iter() {
let s = row.iter().fold("".to_string(), |mut acc, e| {
acc.push_str(&format!("{}", e));
acc
});
final_string.push_str("\n");
final_string.push_str(&s);
}
f.write_str(&final_string)
}
}