1
0
Files
AdventOfCode2025/src/solvers/gift_shop.rs
T
2026-01-30 15:23:42 +01:00

258 lines
8.0 KiB
Rust

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<R: BufRead>(&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<Self> {
// Parses first and last IDs from range string.
let ids: Vec<u64> = 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<u32, Vec<PartsConfig>>,
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<PartsConfig> {
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<PartsConfig> {
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)
);
}
}