use std::fs::File; use std::io::{BufRead, BufReader, Lines}; // BOILERPLATE type InputIter = Lines>; fn get_input() -> InputIter { let f = File::open("input").unwrap(); let br = BufReader::new(f); br.lines() } fn main() { println!("Problem 1 solution: {}", problem1(get_input())); println!("Problem 2 solution: {}", problem2(get_input())); } // PARSE #[derive(Debug)] struct MapCell { dist: u64, kind: char, inside: bool, } impl From for MapCell { fn from(c: char) -> Self { MapCell { dist: 0, kind: c, inside: false, } } } #[derive(Debug)] struct PipeMap { map: Vec>, start: (usize, usize), } const ALL_ADJ: [(isize, isize); 8] = [ (-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1), ]; impl PipeMap { fn adjacencies(c: char) -> Option<[(isize, isize); 2]> { match c { '|' => Some([(0, -1), (0, 1)]), '-' => Some([(-1, 0), (1, 0)]), 'L' => Some([(0, -1), (1, 0)]), 'J' => Some([(0, -1), (-1, 0)]), '7' => Some([(0, 1), (-1, 0)]), 'F' => Some([(0, 1), (1, 0)]), '.' => None, 'S' => None, _ => panic!("unhandled type"), } } fn valid_pos(&self, pos: (isize, isize)) -> bool { pos.0 >= 0 && pos.0 < self.map[0].len() as isize && pos.1 >= 0 && pos.1 < self.map.len() as isize } fn apply_adj(&self, pos: (usize, usize), adj: (isize, isize)) -> Option<(usize, usize)> { if self.valid_pos((pos.0 as isize + adj.0, pos.1 as isize + adj.1)) { Some(( (pos.0 as isize + adj.0) as usize, (pos.1 as isize + adj.1) as usize, )) } else { None } } fn adjacent_positions(&self, pos: (usize, usize)) -> Option<[(usize, usize); 2]> { let adj = Self::adjacencies(self.map[pos.1][pos.0].kind); if let Some(adj) = adj { let mut positions = adj.iter().filter_map(|adj| self.apply_adj(pos, *adj)); Some([positions.next().unwrap(), positions.next().unwrap()]) } else { None } } fn start_adjacencies(&self) -> [(usize, usize); 2] { let mut adj_positions = Vec::new(); for neighbour in ALL_ADJ .iter() .filter_map(|adj| self.apply_adj(self.start, *adj)) { if let Some(neigh_adjs) = self.adjacent_positions(neighbour) { if neigh_adjs.contains(&self.start) { adj_positions.push(neighbour) } } } [adj_positions[0], adj_positions[1]] } fn mark_inside(&mut self) { let start_adj = self.start_adjacencies(); let start_kind = if start_adj.contains( &self .apply_adj(self.start, (0, -1)) .unwrap_or((99999, 99999)), ) && start_adj .contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999))) { '|' } else if start_adj.contains( &self .apply_adj(self.start, (-1, 0)) .unwrap_or((99999, 99999)), ) && start_adj .contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999))) { '-' } else if start_adj.contains( &self .apply_adj(self.start, (0, -1)) .unwrap_or((99999, 99999)), ) && start_adj .contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999))) { 'L' } else if start_adj.contains( &self .apply_adj(self.start, (0, -1)) .unwrap_or((99999, 99999)), ) && start_adj.contains( &self .apply_adj(self.start, (-1, 0)) .unwrap_or((99999, 99999)), ) { 'J' } else if start_adj.contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999))) && start_adj.contains( &self .apply_adj(self.start, (-1, 0)) .unwrap_or((99999, 99999)), ) { '7' } else if start_adj.contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999))) && start_adj.contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999))) { 'F' } else { panic!("invalid start"); }; for row in &mut self.map { let mut inside = false; for cell in row { let mut kind = cell.kind; if cell.dist == 0 && kind != 'S' { cell.inside = inside; } else { if kind == 'S' { kind = start_kind; } if kind == '|' || kind == 'L' || kind == 'J' { inside = !inside; } } } } } } impl From> for PipeMap { fn from(lines: Lines) -> Self { let mut map: Vec> = Vec::new(); for line in lines { map.push(line.unwrap().chars().map(MapCell::from).collect()); } let start = map .iter() .enumerate() .map(|(idx, row)| (row.iter().position(|p| p.kind == 'S'), idx)) .find(|(x, _)| x.is_some()) .unwrap(); let start = (start.0.unwrap(), start.1); let mut pipemap = PipeMap { map, start }; let mut cur_positions = pipemap.start_adjacencies(); let mut cur_distance = 1; loop { for pos in cur_positions { pipemap.map[pos.1][pos.0].dist = cur_distance; } cur_distance += 1; if let Some(new_pos1) = pipemap.adjacent_positions(cur_positions[0]) { if let Some(new_pos1) = new_pos1.iter().find(|pos| { pipemap.map[pos.1][pos.0].dist == 0 && pipemap.map[pos.1][pos.0].kind != 'S' }) { cur_positions[0] = *new_pos1; } else { break; } } if let Some(new_pos2) = pipemap.adjacent_positions(cur_positions[1]) { if let Some(new_pos2) = new_pos2.iter().find(|pos| { pipemap.map[pos.1][pos.0].dist == 0 && pipemap.map[pos.1][pos.0].kind != 'S' }) { cur_positions[1] = *new_pos2; } else { break; } } if cur_positions[0] == cur_positions[1] { pipemap.map[cur_positions[0].1][cur_positions[0].0].dist = cur_distance; break; } } pipemap } } // PROBLEM 1 solution fn problem1(input: Lines) -> u64 { let map = PipeMap::from(input); map.map .iter() .map(|row| row.iter().map(|cell| cell.dist).max().unwrap()) .max() .unwrap() } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { let mut map = PipeMap::from(input); map.mark_inside(); for row in &map.map { for cell in row { print!("{}", cell.kind); } print!(" "); for cell in row { print!( "{}", match cell.inside { true => 'I', false => 'O', } ); } println!(); } map.map .iter() .map(|row| row.iter().filter(|cell| cell.inside).count()) .sum::() as u64 } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; const EXAMPLE: &str = &"..F7. .FJ|. SJ.L7 |F--J LJ..."; const EXAMPLE2: &str = &"FF7FSF7F7F7F7F7F---7 L|LJ||||||||||||F--J FL-7LJLJ||||||LJL-77 F--JF--7||LJLJ7F7FJ- L---JF-JLJ.||-FJLJJ7 |F|F-JF---7F7-L7L|7| |FFJF7L7F-JF7|JL---7 7-L-JL7||F7|L7F-7F7| L.L7LFJ|||||FJL7||LJ L7JLJL-JLJLJL--JLJ.L"; #[test] fn problem1_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem1(c.lines()), 8); } #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE2); assert_eq!(problem2(c.lines()), 10); } }