Add solution for "Day 2: Gift Shop", part 2
This commit is contained in:
Generated
+7
@@ -7,6 +7,7 @@ name = "advent_of_code_2025"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"grid",
|
||||
"primes",
|
||||
"regex",
|
||||
"sanitise-file-name",
|
||||
"scan_fmt",
|
||||
@@ -33,6 +34,12 @@ version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "primes"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0466ef49edd4a5a4bc9d62804a34e89366810bd8bfc3ed537101e3d099f245c5"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
|
||||
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
grid = "1.0.0"
|
||||
primes = "0.4.0"
|
||||
regex = "1.12.2"
|
||||
sanitise-file-name = "1.0.0"
|
||||
scan_fmt = "0.2.6"
|
||||
|
||||
@@ -16,6 +16,14 @@ This project does not contain the puzzle or example inputs as per the [copyright
|
||||
|
||||
For this one, we are moving around a dial with some not too complicated modulo calculations.
|
||||
|
||||
### Day 2: Gift Shop
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2025/day/2>, :white_check_mark: Solver: [`GiftShop`](src/solvers/gift_shop.rs)
|
||||
|
||||
Firstly, we calculate directly the sum of invalid IDs composed of two identical sequences of digits for part 1. This can easily be generalized for any number of repetitions of sequences. However, the problem is to avoid adding certain invalid IDs to the sum multiple times. Therefore we only check prime numbers of repetitions, and take care that invalid IDs that are composed of a repetition of a single digit are only counted once.
|
||||
|
||||
On top of that we use a cache to avoid recalculating prime factors and certain helper variables for given ID lengths and repetition counts.
|
||||
|
||||
### Day 4: Printing Department
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2025/day/4>, :white_check_mark: Solver: [`PrintingDepartment`](src/solvers/printing_department.rs)
|
||||
|
||||
+187
-34
@@ -1,4 +1,6 @@
|
||||
use crate::solvers::Solver;
|
||||
use primes::{PrimeSet, Sieve};
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufRead;
|
||||
|
||||
pub struct GiftShop {}
|
||||
@@ -12,16 +14,21 @@ impl Solver for GiftShop {
|
||||
const PUZZLE_NAME: &'static str = "Gift Shop";
|
||||
|
||||
fn process_data<R: BufRead>(&self, reader: R) -> (u64, u64) {
|
||||
let mut invalid_ids_sum = 0;
|
||||
// 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 {
|
||||
if let Some(range) = Range::try_from_str(range) {
|
||||
invalid_ids_sum += range.sum_invalid_ids();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
(invalid_ids_sum, 0)
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +41,10 @@ struct Range {
|
||||
impl Range {
|
||||
const SPLIT_CHAR: char = '-';
|
||||
|
||||
fn try_from_str(s: &str) -> Option<Self> {
|
||||
// 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)
|
||||
@@ -49,56 +59,199 @@ impl 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 {
|
||||
if a_log % 2 == 0 {
|
||||
// Skips this range if the first and last IDs are both odd.
|
||||
None
|
||||
} else {
|
||||
Some(Range {
|
||||
vec![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 {
|
||||
vec![
|
||||
// Last ID becomes the largest ID with a number of digits equal to 'a_log + 1'.
|
||||
Some(Range {
|
||||
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,
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// 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;
|
||||
// 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) {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
// 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 * (power10 + 1)
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ fn secret_entrance() {
|
||||
#[test]
|
||||
fn gift_shop() {
|
||||
assert_eq!(
|
||||
Ok((1227775554, 0)),
|
||||
Ok((1227775554, 4174379265)),
|
||||
solvers::run_solver(solvers::gift_shop::GiftShop {}, &EXAMPLE_PATHS)
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ fn secret_entrance() {
|
||||
#[test]
|
||||
fn gift_shop() {
|
||||
assert_eq!(
|
||||
Ok((38158151648, 0)),
|
||||
Ok((38158151648, 45283684555)),
|
||||
solvers::run_solver(solvers::gift_shop::GiftShop {}, &solvers::DATA_PATHS)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user