use crate::solvers::Solver; use primes::{PrimeSet, Sieve}; use std::collections::HashMap; 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) { // Cache of part configuration data for number of digits. let mut cache = Cache::new(); let mut result = (0, 0); for line in reader.lines().map_while(Result::ok) { let ranges = line.split(Self::RANGES_SPLIT_CHAR); for range in ranges { for range in Range::from_str(range) { let invalid_id_sums = range.sum_invalid_ids(&mut cache); result.0 += invalid_id_sums.0; result.1 += invalid_id_sums.1; } } } result } } struct Range { first: u64, last: u64, digits: u32, } impl Range { const SPLIT_CHAR: char = '-'; // Returns a vector of Range from a string, such that each element describes an interval of // integers with the same number of digits (base 10), and the intervals combined describe the // full range. fn from_str(s: &str) -> Vec { // 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 != 0 { if a_log == b_log { vec![Range { first: ids[0], last: ids[1], digits: a_log + 1, }] } else { vec![ // Last ID becomes the largest ID with a number of digits equal to 'a_log + 1'. Range { first: ids[0], last: 10_u64.pow(b_log) - 1, digits: b_log, }, // First ID becomes the smallest ID with a number of digits equal to 'a_log + 2'. Range { first: 10_u64.pow(b_log), last: ids[1], digits: b_log + 1, }, ] } } else { // There are no invalid IDs with one digit. if a_log == b_log { vec![] } else { vec![Range { first: 10, last: ids[1], digits: 2, }] } } } // Calculates the sum of invalid IDs contained in this Range. The first number in the returned // tuple is the sum of invalid IDs made from some sequence of digits repeated exactly twice, // while the second number results from considering sequences repeated twice or more. fn sum_invalid_ids(&self, cache: &mut Cache) -> (u64, u64) { let parts_configs = cache.entry(self.digits); // Determines sum of invalid IDs. let mut result = (0, 0); let mut duplicate_correction = 0; for config in parts_configs { let invalid_id_sum = self.sum_invalid_ids_fixed_parts(&config); if config.parts == 2 { result.0 += invalid_id_sum; } result.1 += invalid_id_sum; if config.parts == self.digits { duplicate_correction = invalid_id_sum; } } // This correction ensures that invalid IDs that are composed of a repetition of a single // digit, e.g. '111111' are counted only once. result.1 -= duplicate_correction * (parts_configs.len() as u64 - 1); result } // Calculates the sum of invalid IDs contained in this Range made from a fixed, given number of // repetitions of a sequence of digits. fn sum_invalid_ids_fixed_parts(&self, config: &PartsConfig) -> u64 { let mut sum = 0; let first_left = self.first / config.base; let last_left = self.last / config.base; let first_right = self.first % config.base; let last_right = self.last % config.base; // Checks, for a given range 'ABC...-DEF...', whether invalid ID 'AAA...' is in the range. let target_right = first_left * config.part_base_repeat; if target_right >= first_right && (first_left < last_left || target_right <= 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 'ABC...-DEF...', whether invalid ID 'DDD...' is in the range. let target_right = last_left * config.part_base_repeat; if first_left != last_left && target_right <= last_right { sum += last_left; } sum * (config.base + config.part_base_repeat) } } struct Cache { map: HashMap>, primes: Sieve, } impl Cache { fn new() -> Self { Cache { map: HashMap::new(), primes: Sieve::new(), } } // Gets cached or inserts new parts configurations for number of digits. fn entry(&mut self, digits: u32) -> &Vec { self.map .entry(digits) .or_insert_with(|| Self::build_cache_entry(digits, &mut self.primes)) } fn build_cache_entry(digits: u32, primes: &mut Sieve) -> Vec { let mut result = Vec::new(); result.push(PartsConfig::new(digits, digits)); let max = digits / 2; for i in primes.iter().take_while(|&p| p <= u64::from(max)) { let i = u32::try_from(i).unwrap(); if digits % i == 0 { result.push(PartsConfig::new(digits, i)); } } result } } struct PartsConfig { parts: u32, base: u64, part_base_repeat: u64, } impl PartsConfig { fn new(digits: u32, parts: u32) -> Self { let part_digits = digits / parts; PartsConfig { parts, base: 10_u64.pow(digits - part_digits), part_base_repeat: Self::repeat_base(part_digits, parts - 1), } } fn repeat_base(part_digits: u32, repeat: u32) -> u64 { let base = 10_u64.pow(part_digits); let mut result = 1; for _ in 1..repeat { result = result * base + 1; } result } } #[cfg(test)] mod tests { use super::*; #[test] fn part1() { test_fixed_parts(11, 22, 2, 2, 33); test_fixed_parts(11, 21, 2, 2, 11); test_fixed_parts(12, 22, 2, 2, 22); test_fixed_parts(12, 32, 2, 2, 22); } #[test] fn part2() { test_fixed_parts(111, 333, 3, 3, 666); test_fixed_parts(565653, 565659, 6, 3, 565656); test_fixed_parts(56565300, 56565900, 8, 4, 56565656); } fn test_fixed_parts(first: u64, last: u64, digits: u32, parts: u32, expected: u64) { assert_eq!( Range { first, last, digits } .sum_invalid_ids_fixed_parts(&PartsConfig::new(digits, parts)), expected ); } #[test] fn ignore_duplicates() { assert_eq!( Range { first: 222222, last: 222222, digits: 6 } .sum_invalid_ids(&mut Cache::new()), (222222, 222222) ); } }