Skip to content

Commit 04400d5

Browse files
committed
Day 7 (parallelized)
1 parent 7b97834 commit 04400d5

File tree

1 file changed

+42
-26
lines changed

1 file changed

+42
-26
lines changed

src/day07.rs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{fs, iter::zip, num::ParseIntError, path::PathBuf, str::FromStr};
22

33
use itertools::Itertools;
4+
use rayon::prelude::*;
45

56
pub fn day07(mut input_path: PathBuf) {
67
input_path.push("07.txt");
@@ -35,8 +36,8 @@ fn part1(equations: &[Equation]) -> u64 {
3536

3637
fn part2(equations: &[Equation]) -> u64 {
3738
equations
38-
.iter()
39-
.filter(|e| can_be_true(e, vec![Operator::Add, Operator::Multiply, Operator::Concat]))
39+
.into_par_iter() // this gives speedup of x1.2
40+
.filter(|e| can_be_true_par(e, vec![Operator::Add, Operator::Multiply, Operator::Concat]))
4041
.map(|e| e.result)
4142
.sum()
4243
}
@@ -60,27 +61,35 @@ fn is_true(equation: &Equation, operators: &[Operator]) -> bool {
6061
// we checked during parsing
6162
panic!("RHS needs at least one term");
6263
};
63-
64-
equation.result == zip(operators, tail).fold(*init, |acc, (&op, &t)| eval(acc, op, t))
65-
6664
// Early stopping the fold when we exceed result is surprisingly not appreciably faster.
6765
// this try_fold is similar to (sum . filter ((>) result) . (scan foldop))
6866

69-
// zip(operators, tail)
70-
// .try_fold(*init, |acc, (&op, &t)| {
71-
// let s = eval(acc, op, t);
72-
// if s > equation.result {
73-
// return Err(s);
74-
// }
75-
// Ok(s)
76-
// })
77-
// .is_ok_and(|lhs| lhs == equation.result)
67+
zip(operators, tail)
68+
.try_fold(*init, |acc, (&op, &t)| {
69+
let s = eval(acc, op, t);
70+
if s > equation.result {
71+
return Err(s);
72+
}
73+
Ok(s)
74+
})
75+
.is_ok_and(|lhs| lhs == equation.result)
7876
}
7977

8078
fn can_be_true(equation: &Equation, options: Vec<Operator>) -> bool {
8179
operator_combinations_for(&equation, options).any(|ops| is_true(&equation, &ops))
8280
}
8381

82+
fn can_be_true_par(equation: &Equation, options: Vec<Operator>) -> bool {
83+
// because Combinations has mutable state, we cannot straightforwardly parallelize,
84+
// this is not as well encapsulated by has a speedup of x2.5 (0.8 vs 0.3s).
85+
// still, we only parallize per equation, could parallelize all
86+
let n_ops = (equation.terms.len() - 1) as u32;
87+
(0..options.len().pow(n_ops))
88+
.into_par_iter()
89+
.filter_map(|n| combinations_for(n, n_ops, &options))
90+
.any(|ops| is_true(&equation, &ops))
91+
}
92+
8493
fn operator_combinations_for(
8594
equation: &Equation,
8695
options: Vec<Operator>,
@@ -89,7 +98,7 @@ fn operator_combinations_for(
8998
}
9099

91100
struct Combinations<T> {
92-
// TODO: Don't need to own that
101+
// TODO: Don't need to own that, but would introduce lifetime woes (probably)
93102
options: Vec<T>,
94103
length: u32,
95104
current: usize,
@@ -109,19 +118,26 @@ impl<T: Copy> Iterator for Combinations<T> {
109118
type Item = Vec<T>;
110119

111120
fn next(&mut self) -> Option<Self::Item> {
112-
let k = self.options.len();
113121
self.current += 1;
114-
if self.current > k.pow(self.length) {
115-
return None;
116-
}
117-
let mut v: Vec<T> = Vec::with_capacity(self.length as usize);
118-
let n = self.current - 1;
119-
for i in 0..self.length {
120-
// part 1 was easy, we could map bits to operation: n >> i & 1
121-
v.push(self.options[(n / k.pow(i)) % k]);
122-
}
123-
Some(v)
122+
combinations_for(self.current - 1, self.length, &self.options)
123+
}
124+
}
125+
126+
fn combinations_for<T>(n: usize, len: u32, opts: &[T]) -> Option<Vec<T>>
127+
where
128+
T: Copy,
129+
{
130+
let k = opts.len();
131+
if n >= k.pow(len) {
132+
return None;
133+
};
134+
135+
let mut v: Vec<T> = Vec::with_capacity(len as usize);
136+
for i in 0..len {
137+
// part 1 was easy, we could map bits to operation: n >> i & 1
138+
v.push(opts[(n / k.pow(i)) % k]);
124139
}
140+
Some(v)
125141
}
126142

127143
#[derive(Debug)]

0 commit comments

Comments
 (0)