use num::traits; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use std::ops::Range; // --- Day 3: Gear Ratios --- // You and the Elf eventually reach a gondola lift station; he says the gondola lift // will take you up to the water source, but this is as far as he can bring you. You go // inside. // It doesn't take long to find the gondolas, but there seems to be a problem: they're // not moving. // "Aaah!" // You turn around to see a slightly-greasy Elf with a wrench and a look of surprise. // "Sorry, I wasn't expecting anyone! The gondola lift isn't working right now; it'll // still be a while before I can fix it." You offer to help. // The engineer explains that an engine part seems to be missing from the engine, but // nobody can figure out which one. If you can add up all the part numbers in the engine // schematic, it should be easy to work out which part is missing. // The engine schematic (your puzzle input) consists of a visual representation of the // engine. There are lots of numbers and symbols you don't really understand, but // apparently any number adjacent to a symbol, even diagonally, is a "part number" and // should be included in your sum. (Periods (.) do not count as a symbol.) // Here is an example engine schematic: // 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... // ......755. ...$.*.... .664.598.. // In this schematic, two numbers are not part numbers because they are not adjacent to // a symbol: 114 (top right) and 58 (middle right). Every other number is adjacent to a // symbol and so is a part number; their sum is 4361. // 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())); } // PARSING // We will store the schematic as a 2D Vector of char #[derive(Debug)] struct Pos(usize, usize); // Outer Vec holds rows, so addressing is column then row #[derive(Debug)] struct SchematicRow(Vec); #[derive(Debug)] struct Schematic(Vec); impl From<&str> for SchematicRow { fn from(s: &str) -> Self { SchematicRow(s.chars().collect()) } } impl From for Schematic { fn from(input: InputIter) -> Self { Self(input.map(|line| line.unwrap().as_str().into()).collect()) } } const ADJACENCY_OFFSETS: &[(i64, i64)] = &[ (-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1), ]; // We assume the schematic is composed of equal length lines impl Schematic { fn in_bounds>(&self, x: T, y: T) -> bool { let x_bound: T = self.0[0] .0 .len() .try_into() .unwrap_or_else(|_| panic!("bad bounds")); let y_bound: T = self .0 .len() .try_into() .unwrap_or_else(|_| panic!("bad bounds")); x >= T::zero() && x < x_bound && y >= T::zero() && y < y_bound } fn at(&self, pos: &Pos) -> char { self.0[pos.1].0[pos.0] } fn adjacent_to_symbol(&self, pos: &Pos) -> bool { ADJACENCY_OFFSETS.iter().any(|(x_ofs, y_ofs)| { let adj_x = (pos.0 as i64) + x_ofs; let adj_y = (pos.1 as i64) + y_ofs; if !self.in_bounds(adj_x, adj_y) { false } else { let adj_c = self.at(&Pos(adj_x as usize, adj_y as usize)); adj_c != '.' && adj_c.is_ascii_punctuation() } }) } fn is_gear(&self, pos: &Pos) -> bool { if self.at(pos) != '*' { return false; } ADJACENCY_OFFSETS .iter() .filter(|(x_ofs, y_ofs)| { let adj_x = (pos.0 as i64) + x_ofs; let adj_y = (pos.1 as i64) + y_ofs; if adj_x < 0 || adj_x >= self.0[pos.1].0.len() as i64 || adj_y < 0 || adj_y >= self.0.len() as i64 { false } else { let adj_c = self.at(&Pos(adj_x as usize, adj_y as usize)); adj_c.is_digit(10) } }) .count() >= 2 } fn gear_ratio(&self, pos: &Pos) -> Option { if self.at(pos) != '*' { return None; } let mut exclusions: Vec<(Range, i64)> = Vec::new(); let mut nums = (0u64, 0u64); for (x_ofs, y_ofs) in ADJACENCY_OFFSETS { let adj_x = (pos.0 as i64) + x_ofs; let adj_y = (pos.1 as i64) + y_ofs; if !self.in_bounds(adj_x, adj_y) || exclusions .iter() .any(|(ex_x_range, ex_y)| *ex_y == adj_y && ex_x_range.contains(&(adj_x as usize))) { continue; } let adj_c = self.at(&Pos(adj_x as usize, adj_y as usize)); if adj_c.is_digit(10) { let mut num_start_x = adj_x as usize; loop { if num_start_x > 0 && self .at(&Pos((num_start_x - 1) as usize, adj_y as usize)) .is_digit(10) { num_start_x -= 1; } else { break; } } let mut num_end_x = adj_x as usize; loop { if self.in_bounds(num_end_x + 1, adj_y as usize) && self .at(&Pos((num_end_x + 1) as usize, adj_y as usize)) .is_digit(10) { num_end_x += 1; } else { break; } } // we have the bounds for the number, extract a slice let num_slice = &self.0[adj_y as usize].0[num_start_x..num_end_x + 1]; let mut num = 0u64; for c in num_slice { num = num * 10 + c.to_digit(10).unwrap() as u64; } if nums.0 == 0 { nums.0 = num } else { nums.1 = num; return Some(nums.0 * nums.1); } exclusions.push((num_start_x..num_end_x + 1, adj_y)); } } None } } // PROBLEM 1 solution // Of course, the actual engine schematic is much larger. What is the sum of all of the // part numbers in the engine schematic? fn problem1_all_valid_part_numbers(schematic: &Schematic) -> Vec { let mut results = Vec::new(); let mut cur_pos = Pos(0usize, 0usize); while cur_pos.1 < schematic.0.len() { while cur_pos.0 < schematic.0[0].0.len() { let c = schematic.at(&cur_pos); if c.is_digit(10) { let mut valid: bool = schematic.adjacent_to_symbol(&cur_pos); let mut num = c.to_digit(10).unwrap() as u64; loop { if cur_pos.0 + 1 >= schematic.0[0].0.len() { break; } cur_pos.0 += 1; if schematic.at(&cur_pos).is_digit(10) { num = num * 10 + schematic.at(&cur_pos).to_digit(10).unwrap() as u64; valid = valid || schematic.adjacent_to_symbol(&cur_pos); } else { break; } } if valid { results.push(num); } } cur_pos.0 += 1; } cur_pos.1 += 1; cur_pos.0 = 0; } results } fn problem1(input: InputIter) -> u64 { let schematic: Schematic = input.into(); problem1_all_valid_part_numbers(&schematic).iter().sum() } // PROBLEM 2 solution // --- Part Two --- // The engineer finds the missing part and installs it in the engine! As the engine // springs to life, you jump in the closest gondola, finally ready to ascend to the // water source. // You don't seem to be going very fast, though. Maybe something is still wrong? // Fortunately, the gondola has a phone labeled "help", so you pick it up and the // engineer answers. // Before you can explain the situation, she suggests that you look out the window. // There stands the engineer, holding a phone in one hand and waving with the other. // You're going so slowly that you haven't even left the station. You exit the gondola. // The missing part wasn't the only issue - one of the gears in the engine is wrong. A // gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is // the result of multiplying those two numbers together. // This time, you need to find the gear ratio of every gear and add them all up so that // the engineer can figure out which gear needs to be replaced. // Consider the same engine schematic again: // 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... // ......755. ...$.*.... .664.598.. // In this schematic, there are two gears. The first is in the top left; it has part // numbers 467 and 35, so its gear ratio is 16345. The second gear is in the lower // right; its gear ratio is 451490. (The * adjacent to 617 is not a gear because it is // only adjacent to one part number.) Adding up all of the gear ratios produces 467835. // What is the sum of all of the gear ratios in your engine schematic? fn problem2_all_gear_ratios(schematic: &Schematic) -> Vec { let mut gears = Vec::new(); for y in 0..schematic.0.len() { for x in 0..schematic.0[0].0.len() { match schematic.gear_ratio(&Pos(x, y)) { Some(ratio) => gears.push(ratio), _ => () } } } gears } fn problem2(input: InputIter) -> u64 { let schematic: Schematic = input.into(); problem2_all_gear_ratios(&schematic).iter().sum() }