|
| 1 | +use std::{fs, iter::zip, num::ParseIntError, path::PathBuf, str::FromStr}; |
| 2 | + |
| 3 | +use itertools::Itertools; |
| 4 | + |
| 5 | +pub fn day07(mut input_path: PathBuf) { |
| 6 | + input_path.push("07.txt"); |
| 7 | + let content = fs::read_to_string(input_path).unwrap(); |
| 8 | + |
| 9 | + let equations: Vec<Equation> = content.lines().map(|l| l.parse().unwrap()).collect(); |
| 10 | + |
| 11 | + println!("{:?}", part1(&equations)); |
| 12 | + println!("{:?}", part2(&equations)); |
| 13 | +} |
| 14 | + |
| 15 | +#[derive(Debug)] |
| 16 | +struct Equation { |
| 17 | + result: u64, |
| 18 | + terms: Vec<u64>, |
| 19 | +} |
| 20 | + |
| 21 | +#[derive(Debug, Clone, Copy)] |
| 22 | +enum Operator { |
| 23 | + Add, |
| 24 | + Multiply, |
| 25 | + Concat, |
| 26 | +} |
| 27 | + |
| 28 | +fn part1(equations: &[Equation]) -> u64 { |
| 29 | + equations |
| 30 | + .iter() |
| 31 | + .filter(|e| can_be_true(e, vec![Operator::Add, Operator::Multiply])) |
| 32 | + .map(|e| e.result) |
| 33 | + .sum() |
| 34 | +} |
| 35 | + |
| 36 | +fn part2(equations: &[Equation]) -> u64 { |
| 37 | + equations |
| 38 | + .iter() |
| 39 | + .filter(|e| can_be_true(e, vec![Operator::Add, Operator::Multiply, Operator::Concat])) |
| 40 | + .map(|e| e.result) |
| 41 | + .sum() |
| 42 | +} |
| 43 | + |
| 44 | +fn eval(a: u64, op: Operator, b: u64) -> u64 { |
| 45 | + match op { |
| 46 | + Operator::Add => a + b, |
| 47 | + Operator::Multiply => a * b, |
| 48 | + Operator::Concat => { |
| 49 | + let mut o = 1; |
| 50 | + while o <= b { |
| 51 | + o *= 10; |
| 52 | + } |
| 53 | + a * o + b |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +fn is_true(equation: &Equation, operators: &[Operator]) -> bool { |
| 59 | + let [init, tail @ ..] = equation.terms.as_slice() else { |
| 60 | + // we checked during parsing |
| 61 | + panic!("RHS needs at least one term"); |
| 62 | + }; |
| 63 | + |
| 64 | + equation.result == zip(operators, tail).fold(*init, |acc, (&op, &t)| eval(acc, op, t)) |
| 65 | + |
| 66 | + // Early stopping the fold when we exceed result is surprisingly not appreciably faster. |
| 67 | + // this try_fold is similar to (sum . filter ((>) result) . (scan foldop)) |
| 68 | + |
| 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) |
| 78 | +} |
| 79 | + |
| 80 | +fn can_be_true(equation: &Equation, options: Vec<Operator>) -> bool { |
| 81 | + operator_combinations_for(&equation, options).any(|ops| is_true(&equation, &ops)) |
| 82 | +} |
| 83 | + |
| 84 | +fn operator_combinations_for( |
| 85 | + equation: &Equation, |
| 86 | + options: Vec<Operator>, |
| 87 | +) -> Combinations<Operator> { |
| 88 | + Combinations::new((equation.terms.len() - 1) as u32, options) |
| 89 | +} |
| 90 | + |
| 91 | +struct Combinations<T> { |
| 92 | + // TODO: Don't need to own that |
| 93 | + options: Vec<T>, |
| 94 | + length: u32, |
| 95 | + current: usize, |
| 96 | +} |
| 97 | + |
| 98 | +impl<T> Combinations<T> { |
| 99 | + fn new(length: u32, options: Vec<T>) -> Self { |
| 100 | + Combinations { |
| 101 | + options, |
| 102 | + length, |
| 103 | + current: 0, |
| 104 | + } |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +impl<T: Copy> Iterator for Combinations<T> { |
| 109 | + type Item = Vec<T>; |
| 110 | + |
| 111 | + fn next(&mut self) -> Option<Self::Item> { |
| 112 | + let k = self.options.len(); |
| 113 | + 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) |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +#[derive(Debug)] |
| 128 | +struct ParseEquationError; |
| 129 | + |
| 130 | +// so we can use '?' on Result<_, ParseIntError> in a function returning Result<_, ParseEquationError> |
| 131 | +impl From<ParseIntError> for ParseEquationError { |
| 132 | + fn from(_: ParseIntError) -> Self { |
| 133 | + ParseEquationError |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +impl FromStr for Equation { |
| 138 | + type Err = ParseEquationError; |
| 139 | + |
| 140 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 141 | + let Some((lhs, rhs)) = s.split(":").next_tuple() else { |
| 142 | + return Err(ParseEquationError); |
| 143 | + }; |
| 144 | + let result = lhs.parse()?; |
| 145 | + let terms = rhs |
| 146 | + .split_whitespace() |
| 147 | + .map(|t| t.parse::<u64>()) |
| 148 | + .collect::<Result<Vec<_>, _>>()?; |
| 149 | + match terms.len() { |
| 150 | + 0 => Err(ParseEquationError), |
| 151 | + _ => Ok(Equation { result, terms }), |
| 152 | + } |
| 153 | + } |
| 154 | +} |
0 commit comments