Skip to content

Commit 85e0933

Browse files
committed
2017 day 15
1 parent c928d37 commit 85e0933

File tree

4 files changed

+168
-15
lines changed

4 files changed

+168
-15
lines changed

crates/utils/src/number.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,28 @@ pub fn chinese_remainder<T: SignedInteger>(
324324

325325
Some(sum.rem_euclid(product))
326326
}
327+
328+
/// Calculates `base.pow(exponent) % modulus`.
329+
///
330+
/// # Examples
331+
/// ```
332+
/// # use utils::number::mod_pow;
333+
/// assert_eq!(mod_pow::<u64>(2, 10, 1000), 24);
334+
/// assert_eq!(mod_pow::<u64>(65, 100000, 2147483647), 1085966926);
335+
/// ```
336+
#[inline]
337+
pub fn mod_pow<T: UnsignedInteger>(base: T, exponent: T, modulus: T) -> T {
338+
let mut result = T::ONE;
339+
let mut base = base % modulus;
340+
let mut exponent = exponent;
341+
342+
while exponent > T::ZERO {
343+
if exponent % T::from(2) == T::ONE {
344+
result = (result * base) % modulus;
345+
}
346+
exponent >>= 1;
347+
base = (base * base) % modulus;
348+
}
349+
350+
result
351+
}

crates/year2015/src/day25.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use utils::number::mod_pow;
12
use utils::prelude::*;
23

34
/// Modular exponentiation.
@@ -32,21 +33,6 @@ impl Day25 {
3233
}
3334
}
3435

35-
fn mod_pow(base: u64, exponent: u64, modulus: u64) -> u64 {
36-
let mut result = 1;
37-
let mut base = base % modulus;
38-
let mut exponent = exponent;
39-
40-
while exponent > 0 {
41-
if exponent % 2 == 1 {
42-
result = (result * base) % modulus;
43-
}
44-
exponent >>= 1;
45-
base = (base * base) % modulus;
46-
}
47-
result
48-
}
49-
5036
examples!(Day25 -> (u64, &'static str) [
5137
{
5238
input: "To continue, please consult the code grid in the manual. Enter the code at row 1, column 1.",

crates/year2017/src/day15.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use std::collections::BTreeMap;
2+
use std::sync::atomic::{AtomicU32, Ordering};
3+
use std::sync::Mutex;
4+
use utils::multithreading;
5+
use utils::number::mod_pow;
6+
use utils::prelude::*;
7+
8+
/// Comparing numbers from two simple random number generators.
9+
///
10+
/// Uses a pool of worker threads to calculate batches of the random numbers.
11+
#[derive(Clone, Debug)]
12+
pub struct Day15 {
13+
batches: BTreeMap<u32, BatchResults>,
14+
}
15+
16+
#[derive(Clone, Debug)]
17+
struct BatchResults {
18+
part1_matches: u32,
19+
part2_a_values: Vec<u16>,
20+
part2_b_values: Vec<u16>,
21+
}
22+
23+
const FACTOR_A: u64 = 16807;
24+
const FACTOR_B: u64 = 48271;
25+
const MODULUS: u64 = 2147483647;
26+
27+
const PART1_PAIRS: u32 = 40_000_000;
28+
const PART2_PAIRS: u32 = 5_000_000;
29+
const BATCH_SIZE: u32 = 250_000; // BATCH_SIZE must evenly divide PART1_PAIRS
30+
31+
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+
139+
examples!(Day15 -> (u32, u32) [
140+
{input: "Generator A starts with 65\nGenerator B starts with 8921", part1: 588, part2: 309},
141+
]);

crates/year2017/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ utils::year!(2017 => year2017, ${
1818
12 => day12::Day12,
1919
13 => day13::Day13,
2020
14 => day14::Day14,
21+
15 => day15::Day15,
2122
});

0 commit comments

Comments
 (0)