use crate::solvers::Solver; use std::io::BufRead; pub struct GiftShop {} impl GiftShop { const RANGES_SPLIT_CHAR: char = ','; } impl Solver for GiftShop { const PUZZLE_INDEX: u8 = 2; const PUZZLE_NAME: &'static str = "Gift Shop"; fn process_data(&self, reader: R) -> (u64, u64) { let mut invalid_ids_sum = 0; for line in reader.lines().map_while(Result::ok) { let ranges = line.split(Self::RANGES_SPLIT_CHAR); for range in ranges { if let Some(range) = Range::try_from_str(range) { invalid_ids_sum += range.sum_invalid_ids(); } } } (invalid_ids_sum, 0) } } struct Range { first: u64, last: u64, digits: u32, } impl Range { const SPLIT_CHAR: char = '-'; fn try_from_str(s: &str) -> Option { // Parses first and last IDs from range string. let ids: Vec = s .split(Self::SPLIT_CHAR) .map(|s| { s.parse() .expect("could not parse id range, must be integers") }) .collect(); assert_eq!(ids.len(), 2, "could not parse id range, must be two values"); // Calculates the logs of first and last IDs and checks the size of the range. let (a_log, b_log) = (ids[0].ilog10(), ids[1].ilog10()); assert!(b_log < a_log + 2, "id range is unexpectedly large"); if a_log == b_log { if a_log % 2 == 0 { // Skips this range if the first and last IDs are both odd. None } else { Some(Range { first: ids[0], last: ids[1], digits: a_log + 1, }) } } else { if a_log % 2 == 0 { // First ID becomes the smallest ID with a number of digits equal to 'a_log + 2'. Some(Range { first: 10_u64.pow(b_log), last: ids[1], digits: b_log + 1, }) } else { // Last ID becomes the largest ID with a number of digits equal to 'a_log + 1'. Some(Range { first: ids[0], last: 10_u64.pow(b_log) - 1, digits: b_log, }) } } } fn sum_invalid_ids(&self) -> u64 { let power10 = 10_u64.pow(self.digits / 2); let first_left = self.first / power10; let last_left = self.last / power10; let first_right = self.first % power10; let last_right = self.last % power10; let mut sum = 0; // Checks, for a given range AB-CD, whether invalid ID AA is in the range. if first_left >= first_right && (first_left < last_left || first_left <= last_right) { sum += first_left; } // Calculates the sum of the in-between invalid IDs directly without loop, but we need to // explicitly check whether there are any values. if first_left + 1 <= last_left { sum += (first_left + last_left) * (last_left - first_left - 1) / 2; } // Checks, for a given range AB-CD, whether invalid ID CC is in the range. if first_left != last_left && last_left <= last_right { sum += last_left; } sum * (power10 + 1) } }