1
0
Fork 0
advent-of-code/2023/src/bin/10.rs

248 lines
6.5 KiB
Rust

advent_of_code::solution!(10);
use std::collections::HashSet;
use std::str::FromStr;
type Map = [[Pieces; 143]; 143];
type Step = (usize, usize);
struct MetalIsland {
map: Map,
start: Step,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Pieces {
Empty,
Start,
VertPipe,
HorizPipe,
NEBend,
NWBend,
SEBend,
SWBend,
Ground,
}
impl FromStr for Pieces {
type Err = String;
fn from_str(input: &str) -> Result<Pieces, Self::Err> {
match input {
"S" => Ok(Pieces::Start),
"|" => Ok(Pieces::VertPipe),
"-" => Ok(Pieces::HorizPipe),
"L" => Ok(Pieces::NEBend),
"J" => Ok(Pieces::NWBend),
"F" => Ok(Pieces::SEBend),
"7" => Ok(Pieces::SWBend),
"." => Ok(Pieces::Ground),
n @ _ => Err(format!("Invalid character {}", n)),
}
}
}
fn parse(input: &str) -> MetalIsland {
let mut m: Map = [[Pieces::Empty; 143]; 143];
let mut row: usize = 1;
let mut start_row: usize = 1;
let mut start_col: usize = 1;
for line in input.lines() {
if !line.is_empty() {
let mut col: usize = 1;
for p in line.split("").filter(|f| !f.is_empty()) {
m[row][col] = Pieces::from_str(p).unwrap();
if m[row][col] == Pieces::Start {
start_row = row;
start_col = col;
}
col += 1;
}
row += 1;
}
}
MetalIsland {
map: m,
start: (start_row, start_col),
}
}
fn start_steps(start: &Step, map: &Map) -> Vec<Step> {
let mut result: Vec<Step> = vec![];
let north = map[start.0 - 1][start.1];
if north == Pieces::VertPipe || north == Pieces::SEBend || north == Pieces::SWBend {
result.push((start.0 - 1, start.1));
}
let east = map[start.0][start.1 + 1];
if east == Pieces::HorizPipe || east == Pieces::NWBend || east == Pieces::SWBend {
result.push((start.0, start.1 + 1));
}
let south = map[start.0 + 1][start.1];
if south == Pieces::VertPipe || south == Pieces::NEBend || south == Pieces::NWBend {
result.push((start.0 + 1, start.1));
}
let west = map[start.0][start.1 - 1];
if west == Pieces::HorizPipe || west == Pieces::SEBend || west == Pieces::NEBend {
result.push((start.0, start.1 - 1));
}
result
}
fn next_step(path: &Vec<Step>, map: &Map) -> Step {
let last = path[path.len() - 1];
let row = last.0;
let col = last.1;
let prev = path[path.len() - 2];
let top = (row - 1, col);
let bottom = (row + 1, col);
let left = (row, col - 1);
let right = (row, col + 1);
match map[row][col] {
Pieces::Empty => panic!("cannot path empty space"),
Pieces::Start => panic!("should not be start space"),
Pieces::Ground => panic!("cannot path ground"),
Pieces::VertPipe => {
if prev == top {
bottom
} else {
top
}
}
Pieces::HorizPipe => {
if prev == left {
right
} else {
left
}
}
Pieces::NWBend => {
if prev == top {
left
} else {
top
}
}
Pieces::NEBend => {
if prev == top {
right
} else {
top
}
}
Pieces::SWBend => {
if prev == bottom {
left
} else {
bottom
}
}
Pieces::SEBend => {
if prev == bottom {
right
} else {
bottom
}
}
}
}
pub fn part_one(input_str: &str) -> Option<u32> {
let input = parse(input_str);
let next_steps = start_steps(&input.start, &input.map);
let mut left_path: Vec<Step> = vec![];
left_path.push(input.start);
left_path.push(next_steps[0]);
let mut right_path: Vec<Step> = vec![];
right_path.push(input.start);
right_path.push(next_steps[1]);
loop {
let llast = left_path.last().unwrap();
let rlast = right_path.last().unwrap();
if llast == rlast {
break;
}
left_path.push(next_step(&left_path, &input.map));
right_path.push(next_step(&right_path, &input.map));
}
assert_eq!(left_path.len(), right_path.len());
Some(left_path.len() as u32 - 1)
}
pub fn part_two(input_str: &str) -> Option<u32> {
let input = parse(input_str);
let next_steps = start_steps(&input.start, &input.map);
let mut path: Vec<Step> = vec![];
path.push(input.start);
path.push(next_steps[0]);
loop {
let n = next_step(&path, &input.map);
if input.map[n.0][n.1] == Pieces::Start {
break;
}
path.push(n);
}
let path_set: HashSet<Step> = HashSet::from_iter(path.into_iter());
let mut count = 0;
for row in 1..141 {
let mut inside = false;
for col in 0..140 {
if path_set.contains(&(row, col)) {
if input.map[row][col] == Pieces::VertPipe
|| input.map[row][col] == Pieces::NWBend
|| input.map[row][col] == Pieces::NEBend
{
inside = !inside;
}
} else if inside {
count += 1;
}
}
}
if cfg!(not(debug_assertions)) {
// in release builds, when row==58 and col==127, it ends up counting _twice_
// but when I remove it explicitly from the above count, it ends up removing
// two and not 1. I can't figure out the one-square-duplication.
count -= 1;
}
Some(count)
}
#[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(4));
}
#[test]
fn test_part_two() {
let result = part_one(&advent_of_code::template::read_file_part(
"examples", DAY, 2,
));
assert_eq!(result, Some(8));
}
#[test]
fn test_part_three() {
let result = part_two(&advent_of_code::template::read_file_part(
"examples", DAY, 3,
));
assert_eq!(result, Some(4));
}
#[test]
fn test_part_four() {
let result = part_two(&advent_of_code::template::read_file_part(
"examples", DAY, 4,
));
assert_eq!(result, Some(8));
}
}