|
| 1 | +use std::{iter::from_fn, num::ParseIntError, str::FromStr}; |
| 2 | +use thiserror::Error; |
| 3 | + |
| 4 | +type StoreInt = u64; |
| 5 | +type CalcInt = i128; |
| 6 | + |
| 7 | +const PART2_SHIFT: StoreInt = 10000000000000; |
| 8 | + |
| 9 | +const_assert!(u64::MAX > 2 * PART2_SHIFT); |
| 10 | +const_assert!(i128::MAX > 4 * PART2_SHIFT as CalcInt * PART2_SHIFT as CalcInt); |
| 11 | + |
| 12 | +pub fn day13(input: &str) -> (usize, usize) { |
| 13 | + let machines = Machine::parse_all(input) |
| 14 | + .filter_map(|m| m.ok()) |
| 15 | + .collect::<Vec<_>>(); |
| 16 | + |
| 17 | + (part1(&machines), part2(&machines)) |
| 18 | +} |
| 19 | + |
| 20 | +fn part1(machines: &[Machine]) -> usize { |
| 21 | + machines |
| 22 | + .iter() |
| 23 | + .filter_map(|m| m.solve()) |
| 24 | + .map(|s| s.cost()) |
| 25 | + .sum() |
| 26 | +} |
| 27 | + |
| 28 | +fn part2(machines: &[Machine]) -> usize { |
| 29 | + machines |
| 30 | + .iter() |
| 31 | + .map(|m| Machine { |
| 32 | + prize: m.prize.map(|x| x + PART2_SHIFT), |
| 33 | + a_action: m.a_action, |
| 34 | + b_action: m.b_action, |
| 35 | + }) |
| 36 | + .filter_map(|m| m.solve()) |
| 37 | + .map(|s| s.cost()) |
| 38 | + .sum() |
| 39 | +} |
| 40 | + |
| 41 | +#[derive(Debug, PartialEq, Eq)] |
| 42 | +struct Machine { |
| 43 | + prize: [StoreInt; 2], |
| 44 | + a_action: [StoreInt; 2], |
| 45 | + b_action: [StoreInt; 2], |
| 46 | +} |
| 47 | + |
| 48 | +#[derive(Error, Debug)] |
| 49 | +enum MachineParseError { |
| 50 | + #[error("Wrong Format")] |
| 51 | + WrongFormat(#[from] ParseIntError), |
| 52 | + #[error("Not enough Data")] |
| 53 | + NotEnoughData, |
| 54 | +} |
| 55 | + |
| 56 | +impl Machine { |
| 57 | + fn solve(&self) -> Option<Solution> { |
| 58 | + // prize = a_presses * a_action + b_presses * b_action |
| 59 | + // is a 2x2 LSE, use Cramer's rule to solve |
| 60 | + let [p1, p2] = self.prize.map(|x| x as CalcInt); |
| 61 | + let [a1, a2] = self.a_action.map(|x| x as CalcInt); |
| 62 | + let [b1, b2] = self.b_action.map(|x| x as CalcInt); |
| 63 | + |
| 64 | + let det = a1 * b2 - a2 * b1; |
| 65 | + |
| 66 | + if det == 0 { |
| 67 | + return None; |
| 68 | + } |
| 69 | + |
| 70 | + let (a_presses, a_rem) = divmod(b2 * p1 - b1 * p2, det); |
| 71 | + let (b_presses, b_rem) = divmod(a1 * p2 - a2 * p1, det); |
| 72 | + |
| 73 | + if a_presses < 0 || a_rem != 0 || b_presses < 0 || b_rem != 0 { |
| 74 | + return None; |
| 75 | + } |
| 76 | + |
| 77 | + return StoreInt::try_from(a_presses) |
| 78 | + .ok() |
| 79 | + .zip_with(StoreInt::try_from(b_presses).ok(), Solution::new); |
| 80 | + |
| 81 | + fn divmod(x: CalcInt, y: CalcInt) -> (CalcInt, CalcInt) { |
| 82 | + (x / y, x % y) |
| 83 | + } |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +impl FromStr for Machine { |
| 88 | + type Err = MachineParseError; |
| 89 | + |
| 90 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 91 | + fn parse_button(line: &str, name: &str) -> Result<(StoreInt, StoreInt), MachineParseError> { |
| 92 | + line.strip_prefix(&format!("Button {}: X+", name)) |
| 93 | + .and_then(|rest| rest.split_once(", Y+")) |
| 94 | + .ok_or(MachineParseError::NotEnoughData) // Would want WrongFormat, but then the typechecker gets confused |
| 95 | + .and_then(|(x, y)| Ok((x.parse()?, y.parse()?))) |
| 96 | + } |
| 97 | + |
| 98 | + fn parse_prize(line: &str) -> Result<(StoreInt, StoreInt), MachineParseError> { |
| 99 | + line.strip_prefix("Prize: X=") |
| 100 | + .and_then(|rest| rest.split_once(", Y=")) |
| 101 | + .ok_or(MachineParseError::NotEnoughData) |
| 102 | + .and_then(|(x, y)| Ok((x.parse()?, y.parse()?))) |
| 103 | + } |
| 104 | + |
| 105 | + let mut lines = s.lines(); |
| 106 | + let a_line = lines.next().ok_or(MachineParseError::NotEnoughData)?; |
| 107 | + let b_line = lines.next().ok_or(MachineParseError::NotEnoughData)?; |
| 108 | + let p_line = lines.next().ok_or(MachineParseError::NotEnoughData)?; |
| 109 | + |
| 110 | + let (a_x, a_y) = parse_button(a_line, "A")?; |
| 111 | + let (b_x, b_y) = parse_button(b_line, "B")?; |
| 112 | + let (p_x, p_y) = parse_prize(p_line)?; |
| 113 | + |
| 114 | + Ok(Machine { |
| 115 | + prize: [p_x, p_y], |
| 116 | + a_action: [a_x, a_y], |
| 117 | + b_action: [b_x, b_y], |
| 118 | + }) |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +trait ParseIncrementally: FromStr { |
| 123 | + const NUM_LINES: usize; |
| 124 | + |
| 125 | + fn incremental_parse(s: &str) -> (Result<Self, Self::Err>, &str) { |
| 126 | + fn split_n_newlines(n: usize, s: &str) -> (&str, &str) { |
| 127 | + if n == 0 { |
| 128 | + return ("", &s); |
| 129 | + } |
| 130 | + let newlines: Vec<_> = s.match_indices('\n').take(n).collect(); |
| 131 | + if newlines.len() == n { |
| 132 | + let split_pos = newlines[n - 1].0; |
| 133 | + (&s[..split_pos], &s[split_pos + 1..]) |
| 134 | + } else { |
| 135 | + (&s, "") |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + let (start, rest) = split_n_newlines(Self::NUM_LINES, s); |
| 140 | + (start.parse(), rest) |
| 141 | + } |
| 142 | + |
| 143 | + fn parse_all(s: &str) -> impl Iterator<Item = Result<Self, Self::Err>> { |
| 144 | + let mut rem = s; |
| 145 | + from_fn(move || { |
| 146 | + if rem.is_empty() { |
| 147 | + return None; |
| 148 | + } |
| 149 | + let (result, rest) = Self::incremental_parse(rem); |
| 150 | + rem = rest; |
| 151 | + Some(result) |
| 152 | + }) |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +impl ParseIncrementally for Machine { |
| 157 | + const NUM_LINES: usize = 4; |
| 158 | +} |
| 159 | + |
| 160 | +#[derive(Debug, PartialEq, Eq)] |
| 161 | +struct Solution { |
| 162 | + a_presses: StoreInt, |
| 163 | + b_presses: StoreInt, |
| 164 | +} |
| 165 | + |
| 166 | +impl Solution { |
| 167 | + fn new(a_presses: StoreInt, b_presses: StoreInt) -> Self { |
| 168 | + Self { |
| 169 | + a_presses, |
| 170 | + b_presses, |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + fn cost(&self) -> usize { |
| 175 | + self.a_presses as usize * 3 + self.b_presses as usize * 1 |
| 176 | + } |
| 177 | +} |
0 commit comments