248 lines
6.5 KiB
Rust
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));
|
|
}
|
|
}
|