11use std:: { fs, iter:: zip, num:: ParseIntError , path:: PathBuf , str:: FromStr } ;
22
33use itertools:: Itertools ;
4+ use rayon:: prelude:: * ;
45
56pub fn day07 ( mut input_path : PathBuf ) {
67 input_path. push ( "07.txt" ) ;
@@ -35,8 +36,8 @@ fn part1(equations: &[Equation]) -> u64 {
3536
3637fn 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
8078fn 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+
8493fn operator_combinations_for (
8594 equation : & Equation ,
8695 options : Vec < Operator > ,
@@ -89,7 +98,7 @@ fn operator_combinations_for(
8998}
9099
91100struct 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