1
0
Fork 0

day 10 solution

main
Andrew Coleman 2023-12-11 14:34:26 -05:00
parent 5b2760e9f7
commit e7a3ec0ea1
2 changed files with 279 additions and 0 deletions

278
2023/src/day10.rs Normal file
View File

@ -0,0 +1,278 @@
use aoc_runner_derive::{aoc, aoc_generator};
use std::collections::HashSet;
use std::str::FromStr;
type Map = [[Pieces; 143]; 143];
type Step = (usize, usize);
struct MetalIsland {
map: Map,
start: Step,
}
#[derive(Debug, 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)),
}
}
}
#[aoc_generator(day10)]
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
}
}
}
}
#[aoc(day10, part1)]
fn part1(input: &MetalIsland) -> u64 {
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());
left_path.len() as u64 - 1
}
#[aoc(day10, part2)]
fn part2(input: &MetalIsland) -> u64 {
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;
}
count
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_DATA: &'static str = r#".....
.S-7.
.|.|.
.L-J.
.....
"#;
#[test]
fn sample_data() {
let input = parse(&SAMPLE_DATA);
assert_eq!(part1(&input), 4);
}
const SAMPLE_DATA2: &'static str = r#"..F7.
.FJ|.
SJ.L7
|F--J
LJ...
"#;
#[test]
fn sample_data2() {
let input = parse(&SAMPLE_DATA2);
assert_eq!(part1(&input), 8);
}
const SAMPLE_DATA3: &'static str = r#"..........
.S------7.
.|F----7|.
.||....||.
.||....||.
.|L-7F-J|.
.|..||..|.
.L--JL--J.
..........
"#;
#[test]
fn sample_data3() {
let input = parse(&SAMPLE_DATA3);
assert_eq!(part2(&input), 4);
}
const SAMPLE_DATA4: &'static str = r#".F----7F7F7F7F-7....
.|F--7||||||||FJ....
.||.FJ||||||||L7....
FJL7L7LJLJ||LJ.L-7..
L--J.L7...LJS7F-7L7.
....F-J..F7FJ|L7L7L7
....L7.F7||L7|.L7L7|
.....|FJLJ|FJ|F7|.LJ
....FJL-7.||.||||...
....L---J.LJ.LJLJ...
"#;
#[test]
fn sample_data4() {
let input = parse(&SAMPLE_DATA4);
assert_eq!(part2(&input), 8);
}
}

View File

@ -9,5 +9,6 @@ mod day06;
mod day07;
mod day08;
mod day09;
mod day10;
aoc_lib! { year = 2023 }