1use std::collections::BTreeMap;
2use std::sync::atomic::{AtomicU32, Ordering};
3use std::sync::Mutex;
4use utils::multithreading;
5use utils::number::mod_pow;
6use utils::prelude::*;
7
8#[derive(Clone, Debug)]
12pub struct Day15 {
13 batches: BTreeMap<u32, BatchResults>,
14}
15
16#[derive(Clone, Debug)]
17struct BatchResults {
18 part1_matches: u32,
19 part2_a_values: Vec<u16>,
20 part2_b_values: Vec<u16>,
21}
22
23const FACTOR_A: u64 = 16807;
24const FACTOR_B: u64 = 48271;
25const MODULUS: u64 = 2147483647;
26
27const PART1_PAIRS: u32 = 40_000_000;
28const PART2_PAIRS: u32 = 5_000_000;
29const BATCH_SIZE: u32 = 250_000; impl Day15 {
32 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
33 let (start_a, start_b) = parser::u32()
34 .with_prefix("Generator A starts with ")
35 .with_suffix(parser::eol())
36 .then(parser::u32().with_prefix("Generator B starts with "))
37 .parse_complete(input)?;
38
39 let mutex = Mutex::default();
40 let next_index = AtomicU32::new(0);
41 let part2_a_count = AtomicU32::new(0);
42 let part2_b_count = AtomicU32::new(0);
43
44 multithreading::worker_pool(|| {
45 Self::values_worker(
46 start_a as u64,
47 start_b as u64,
48 &mutex,
49 &next_index,
50 &part2_a_count,
51 &part2_b_count,
52 );
53 });
54
55 Ok(Self {
56 batches: mutex.into_inner().unwrap(),
57 })
58 }
59
60 #[must_use]
61 pub fn part1(&self) -> u32 {
62 self.batches
63 .range(0..PART1_PAIRS)
64 .map(|(_, BatchResults { part1_matches, .. })| part1_matches)
65 .sum()
66 }
67
68 #[must_use]
69 pub fn part2(&self) -> u32 {
70 let a_values = self
71 .batches
72 .values()
73 .flat_map(|BatchResults { part2_a_values, .. }| part2_a_values.iter().copied());
74 let b_values = self
75 .batches
76 .values()
77 .flat_map(|BatchResults { part2_b_values, .. }| part2_b_values.iter().copied());
78
79 a_values
80 .zip(b_values)
81 .take(PART2_PAIRS as usize)
82 .filter(|&(a, b)| a == b)
83 .count() as u32
84 }
85
86 fn values_worker(
87 start_a: u64,
88 start_b: u64,
89 mutex: &Mutex<BTreeMap<u32, BatchResults>>,
90 next_index: &AtomicU32,
91 part2_a_count: &AtomicU32,
92 part2_b_count: &AtomicU32,
93 ) {
94 loop {
95 let start_index = next_index.fetch_add(BATCH_SIZE, Ordering::AcqRel);
96 let part2_a_finished = part2_a_count.load(Ordering::Acquire) >= PART2_PAIRS;
97 let part2_b_finished = part2_b_count.load(Ordering::Acquire) >= PART2_PAIRS;
98 if start_index >= PART1_PAIRS && part2_a_finished && part2_b_finished {
99 break;
100 }
101
102 let mut part1_matches = 0;
103 let mut part2_a_values = Vec::with_capacity(if part2_a_finished { 0 } else { 65536 });
104 let mut part2_b_values = Vec::with_capacity(if part2_b_finished { 0 } else { 32768 });
105
106 let mut a = start_a * mod_pow(FACTOR_A, start_index as u64, MODULUS);
107 let mut b = start_b * mod_pow(FACTOR_B, start_index as u64, MODULUS);
108 for _ in 0..BATCH_SIZE {
109 a = (a * FACTOR_A) % MODULUS;
110 b = (b * FACTOR_B) % MODULUS;
111
112 if a as u16 == b as u16 {
113 part1_matches += 1;
114 }
115 if !part2_a_finished && a % 4 == 0 {
116 part2_a_values.push(a as u16);
117 }
118 if !part2_b_finished && b % 8 == 0 {
119 part2_b_values.push(b as u16);
120 }
121 }
122
123 part2_a_count.fetch_add(part2_a_values.len() as u32, Ordering::AcqRel);
124 part2_b_count.fetch_add(part2_b_values.len() as u32, Ordering::AcqRel);
125
126 let mut batches_guard = mutex.lock().unwrap();
127 batches_guard.insert(
128 start_index,
129 BatchResults {
130 part1_matches,
131 part2_a_values,
132 part2_b_values,
133 },
134 );
135 }
136 }
137}
138
139examples!(Day15 -> (u32, u32) [
140 {input: "Generator A starts with 65\nGenerator B starts with 8921", part1: 588, part2: 309},
141]);