Skip to content

Commit

Permalink
day 16, but with brute force
Browse files Browse the repository at this point in the history
  • Loading branch information
kcaffrey committed Dec 16, 2023
1 parent 30cbee5 commit 867678f
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
| [Day 13](./src/bin/13.rs) | `12.3µs` | `15.9µs` |
| [Day 14](./src/bin/14.rs) | `25.0µs` | `4.5ms` |
| [Day 15](./src/bin/15.rs) | `20.4µs` | `85.9µs` |
| [Day 16](./src/bin/16.rs) | `1.6ms` | `87.7ms` |

**Total: 7.02ms**
**Total: 96.32ms**
<!--- benchmarking table --->

---
Expand Down
10 changes: 10 additions & 0 deletions data/examples/16.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....
327 changes: 327 additions & 0 deletions src/bin/16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
use std::{
collections::{HashSet, VecDeque},
fmt::{Debug, Display, Write},
str::FromStr,
};

use rayon::iter::{ParallelBridge, ParallelIterator};
use thiserror::Error;

advent_of_code::solution!(16);

pub fn part_one(input: &str) -> Option<u32> {
let grid: Grid = input.parse().expect("valid input");
Some(energize_count(&grid, coord!(0, 0), Direction::East))
}

pub fn part_two(input: &str) -> Option<u32> {
let grid: Grid = input.parse().expect("valid input");
(0..grid.height)
.map(|h| (coord!(h, 0), Direction::East))
.chain((0..grid.height).map(|h| (coord!(h, grid.width - 1), Direction::West)))
.chain((0..grid.width).map(|w| (coord!(0, w), Direction::South)))
.chain((0..grid.width).map(|w| (coord!(grid.height - 1, w), Direction::North)))
.par_bridge()
.map(|(c, d)| energize_count(&grid, c, d))
.max()
}

fn energize_count(grid: &Grid, start: Coordinate, start_dir: Direction) -> u32 {
let mut visited = HashSet::new();
let mut visited_coords = HashSet::new();
let mut queue = VecDeque::new();
visited.insert((start, start_dir));
visited_coords.insert(start);
queue.push_back((start, start_dir));
while let Some((cur, dir)) = queue.pop_front() {
for (next, next_dir) in grid.neighbors(cur, dir) {
if visited.insert((next, next_dir)) {
visited_coords.insert(next);
queue.push_back((next, next_dir));
}
}
}
visited_coords.len() as u32
}

struct Grid {
tiles: Vec<Vec<Tile>>,
width: usize,
height: usize,
}

#[derive(Copy, Clone, PartialEq, Eq)]
enum Tile {
Empty,
RightMirror,
LeftMirror,
VerticalSplitter,
HorizontalSplitter,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Coordinate {
row: usize,
col: usize,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum Direction {
North,
South,
East,
West,
}

impl Grid {
pub fn neighbors(
&self,
coord: Coordinate,
dir: Direction,
) -> impl Iterator<Item = (Coordinate, Direction)> + '_ {
std::iter::once(self.get_tile(coord))
.flatten()
.flat_map(move |tile| tile.next(dir))
.filter_map(move |dir| self.move_in_dir(coord, dir).map(|c| (c, dir)))
}

fn get_tile(&self, coord: Coordinate) -> Option<Tile> {
self.tiles
.get(coord.row)
.and_then(|row| row.get(coord.col))
.copied()
}

fn move_in_dir(&self, coord: Coordinate, dir: Direction) -> Option<Coordinate> {
coord
.move_in_dir(dir)
.filter(|&c| c.row < self.height && c.col < self.width)
}
}

impl Tile {
pub fn next(self, dir: Direction) -> impl Iterator<Item = Direction> {
let mut next = [None, None, None];
if self.can_go_straight(dir) {
next[0] = Some(dir);
}
if self.can_turn_left(dir) {
next[1] = Some(dir.rotate_left());
}
if self.can_turn_right(dir) {
next[2] = Some(dir.rotate_right());
}
next.into_iter().flatten()
}

const fn can_go_straight(self, dir: Direction) -> bool {
matches!(
(self, dir),
(Self::Empty, _)
| (Self::VerticalSplitter, Direction::South)
| (Self::VerticalSplitter, Direction::North)
| (Self::HorizontalSplitter, Direction::East)
| (Self::HorizontalSplitter, Direction::West)
)
}

const fn can_turn_left(self, dir: Direction) -> bool {
matches!(
(self, dir),
(Self::VerticalSplitter, Direction::East)
| (Self::VerticalSplitter, Direction::West)
| (Self::HorizontalSplitter, Direction::North)
| (Self::HorizontalSplitter, Direction::South)
| (Self::LeftMirror, Direction::North)
| (Self::LeftMirror, Direction::South)
| (Self::RightMirror, Direction::East)
| (Self::RightMirror, Direction::West)
)
}

const fn can_turn_right(self, dir: Direction) -> bool {
matches!(
(self, dir),
(Self::VerticalSplitter, Direction::East)
| (Self::VerticalSplitter, Direction::West)
| (Self::HorizontalSplitter, Direction::North)
| (Self::HorizontalSplitter, Direction::South)
| (Self::LeftMirror, Direction::East)
| (Self::LeftMirror, Direction::West)
| (Self::RightMirror, Direction::North)
| (Self::RightMirror, Direction::South)
)
}
}

impl Coordinate {
const fn new(row: usize, col: usize) -> Self {
Self { row, col }
}

const fn move_in_dir(self, dir: Direction) -> Option<Self> {
Some(match dir {
Direction::North => {
if self.row == 0 {
return None;
}
Self::new(self.row - 1, self.col)
}
Direction::South => Self::new(self.row + 1, self.col),
Direction::East => Self::new(self.row, self.col + 1),
Direction::West => {
if self.col == 0 {
return None;
}
Self::new(self.row, self.col - 1)
}
})
}
}

impl Direction {
const fn rotate_left(self) -> Self {
match self {
Direction::North => Direction::West,
Direction::South => Direction::East,
Direction::East => Direction::North,
Direction::West => Direction::South,
}
}

const fn rotate_right(self) -> Self {
match self {
Direction::North => Direction::East,
Direction::South => Direction::West,
Direction::East => Direction::South,
Direction::West => Direction::North,
}
}
}

#[derive(Error, Debug)]
enum ParseGridError {
#[error("invalid tile character: {0}")]
InvalidCharacter(char),

#[error("only one row was in the input, expected more than one row")]
OnlyOneRowFound,
}

impl FromStr for Grid {
type Err = ParseGridError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.as_bytes();
let width = s
.iter()
.position(|&ch| ch == b'\n')
.ok_or(ParseGridError::OnlyOneRowFound)?;
let mut rows = Vec::new();
for line in s.split(|&ch| ch == b'\n') {
if line.is_empty() {
continue;
}
rows.push(
line.iter()
.map(|&ch| {
Ok(match ch {
b'.' => Tile::Empty,
b'\\' => Tile::LeftMirror,
b'/' => Tile::RightMirror,
b'|' => Tile::VerticalSplitter,
b'-' => Tile::HorizontalSplitter,
_ => return Err(ParseGridError::InvalidCharacter(ch.into())),
})
})
.collect::<Result<_, _>>()?,
);
}
Ok(Self {
width,
height: rows.len(),
tiles: rows,
})
}
}

impl Display for Grid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for row in &self.tiles {
for tile in row {
write!(f, "{}", tile)?;
}
f.write_char('\n')?;
}
Ok(())
}
}

impl Display for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Tile::Empty => f.write_char('.'),
Tile::RightMirror => f.write_char('/'),
Tile::LeftMirror => f.write_char('\\'),
Tile::VerticalSplitter => f.write_char('|'),
Tile::HorizontalSplitter => f.write_char('-'),
}
}
}

impl Debug for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self, f)
}
}

#[macro_export]
macro_rules! coord {
($x:expr, $y:expr) => {
Coordinate::new($x, $y)
};
}

#[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(46));
}

#[test]
fn test_part_two() {
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(51));
}

#[test]
fn test_grid_neighbors() {
use Direction::{East, North, South, West};
let grid: Grid = advent_of_code::template::read_file("examples", DAY)
.parse()
.unwrap();
let neighbors_vec = |coord, dir| grid.neighbors(coord, dir).collect::<Vec<_>>();
assert_eq!(neighbors_vec(coord!(0, 0), North), vec![]);
assert_eq!(neighbors_vec(coord!(0, 0), West), vec![]);
assert_eq!(
neighbors_vec(coord!(0, 0), East),
vec![(coord!(0, 1), East)]
);
assert_eq!(
neighbors_vec(coord!(0, 1), East),
vec![(coord!(1, 1), South)]
);
assert_eq!(
neighbors_vec(coord!(1, 2), South),
vec![(coord!(1, 3), East), (coord!(1, 1), West)]
);
assert_eq!(
neighbors_vec(coord!(1, 4), West),
vec![(coord!(0, 4), North)]
);
}
}

0 comments on commit 867678f

Please sign in to comment.