diff --git a/2023/src/day10.rs b/2023/src/day10.rs new file mode 100644 index 0000000..17ad403 --- /dev/null +++ b/2023/src/day10.rs @@ -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 { + 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 { + let mut result: Vec = 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, 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 = vec![]; + left_path.push(input.start); + left_path.push(next_steps[0]); + let mut right_path: Vec = 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 = 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 = 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); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index 63c5ba7..738b25c 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -9,5 +9,6 @@ mod day06; mod day07; mod day08; mod day09; +mod day10; aoc_lib! { year = 2023 }