|
1 | 1 | //! # Factory |
2 | 2 | use crate::util::bitset::*; |
3 | 3 | use crate::util::parse::*; |
| 4 | +use crate::util::thread::*; |
| 5 | +use std::collections::BTreeSet; |
4 | 6 |
|
5 | | -type Machine = (u64, Vec<u64>, Vec<u64>); |
6 | | - |
7 | | -pub fn parse(input: &str) -> Vec<Machine> { |
8 | | - let mut tokens = Vec::new(); |
9 | | - |
10 | | - input |
11 | | - .lines() |
12 | | - .map(|line| { |
13 | | - tokens.clear(); |
14 | | - tokens.extend(line.split_ascii_whitespace()); |
15 | | - |
16 | | - let last = tokens.len() - 1; |
17 | | - let lights = tokens[0] |
18 | | - .bytes() |
19 | | - .skip(1) |
20 | | - .enumerate() |
21 | | - .fold(0, |acc, (i, b)| acc | (u64::from(b == b'#') << i)); |
22 | | - let buttons = tokens[1..last] |
23 | | - .iter() |
24 | | - .map(|token| token.iter_unsigned::<u64>().fold(0, |acc, i| acc | (1 << i))) |
25 | | - .collect(); |
26 | | - let joltages = tokens[last].iter_unsigned::<u64>().collect(); |
27 | | - |
28 | | - (lights, buttons, joltages) |
| 7 | +type Machine = (usize, Vec<usize>, Vec<i32>); |
| 8 | +type Input = (Vec<i32>, Vec<i32>); |
| 9 | + |
| 10 | +/// Each machine can be processed independently, parallelizing the work over multiple threads. |
| 11 | +pub fn parse(input: &str) -> Input { |
| 12 | + let items: Vec<_> = input.lines().collect(); |
| 13 | + |
| 14 | + let result: Vec<Vec<_>> = spawn_parallel_iterator(&items, |iter| { |
| 15 | + iter.map(|line| { |
| 16 | + let machine = parse_machine(line); |
| 17 | + (configure_lights(&machine), configure_joltages(&machine)) |
29 | 18 | }) |
30 | 19 | .collect() |
| 20 | + }); |
| 21 | + |
| 22 | + result.into_iter().flatten().unzip() |
| 23 | +} |
| 24 | + |
| 25 | +pub fn part1(input: &Input) -> i32 { |
| 26 | + let (presses, _) = input; |
| 27 | + presses.iter().sum() |
| 28 | +} |
| 29 | + |
| 30 | +pub fn part2(input: &Input) -> i32 { |
| 31 | + let (_, presses) = input; |
| 32 | + presses.iter().sum() |
31 | 33 | } |
32 | 34 |
|
33 | | -pub fn part1(input: &[Machine]) -> u32 { |
34 | | - input |
| 35 | +/// Convert light patterns and buttons to bitmasks to speed up part one. |
| 36 | +fn parse_machine(line: &str) -> Machine { |
| 37 | + let tokens: Vec<_> = line.split_ascii_whitespace().collect(); |
| 38 | + let last = tokens.len() - 1; |
| 39 | + |
| 40 | + let lights = tokens[0] |
| 41 | + .bytes() |
| 42 | + .skip(1) |
| 43 | + .enumerate() |
| 44 | + .fold(0, |light, (i, b)| light | (usize::from(b == b'#') << i)); |
| 45 | + let buttons = tokens[1..last] |
35 | 46 | .iter() |
36 | | - .map(|(lights, buttons, _)| { |
37 | | - let limit = 1 << buttons.len(); |
38 | | - let mut set = 0; |
39 | | - |
40 | | - loop { |
41 | | - set += 1; |
42 | | - let mut n = (1 << set) - 1; |
43 | | - |
44 | | - while n < limit { |
45 | | - if *lights == n.biterator().fold(0, |acc, i| acc ^ buttons[i]) { |
46 | | - return set; |
47 | | - } |
48 | | - n = next_same_bits(n); |
49 | | - } |
50 | | - } |
51 | | - }) |
52 | | - .sum() |
| 47 | + .map(|token| token.iter_unsigned::<usize>().fold(0, |button, i| button | (1 << i))) |
| 48 | + .collect(); |
| 49 | + let joltages = tokens[last].iter_signed::<i32>().collect(); |
| 50 | + |
| 51 | + (lights, buttons, joltages) |
53 | 52 | } |
54 | 53 |
|
55 | | -pub fn part2(_input: &[Machine]) -> u32 { |
56 | | - 123456789 |
| 54 | +/// Check all patterns with one set bit, then pattern with two sets bits and so on, until we find |
| 55 | +/// a match. |
| 56 | +fn configure_lights((lights, buttons, _): &Machine) -> i32 { |
| 57 | + let limit = 1 << buttons.len(); |
| 58 | + let mut set = 0; |
| 59 | + |
| 60 | + loop { |
| 61 | + set += 1; |
| 62 | + let mut n = (1 << set) - 1; |
| 63 | + |
| 64 | + while n < limit { |
| 65 | + if *lights == n.biterator().fold(0, |acc, i| acc ^ buttons[i]) { |
| 66 | + return set; |
| 67 | + } |
| 68 | + n = next_same_bits(n); |
| 69 | + } |
| 70 | + } |
57 | 71 | } |
58 | 72 |
|
| 73 | +/// Find the next highest integer with the same number of one bits as the previous integer, |
| 74 | +/// for example 1011 => 1110. |
59 | 75 | fn next_same_bits(n: i32) -> i32 { |
60 | 76 | let smallest = n & -n; |
61 | 77 | let ripple = n + smallest; |
62 | 78 | let ones = n ^ ripple; |
63 | 79 | let next = (ones >> 2) / smallest; |
64 | 80 | ripple | next |
65 | 81 | } |
| 82 | + |
| 83 | +/// Convert the buttons and joltages to equations, then use |
| 84 | +/// [Gaussian Elimination](https://en.wikipedia.org/wiki/Gaussian_elimination) to reduce the |
| 85 | +/// dimensioanlity of the problem to only the free variables. |
| 86 | +fn configure_joltages((_, buttons, joltages): &Machine) -> i32 { |
| 87 | + let width = buttons.len(); |
| 88 | + let height = joltages.len(); |
| 89 | + let mut equations = vec![vec![0; width + 1]; height]; |
| 90 | + let mut limit = vec![i32::MAX; width]; |
| 91 | + let mut previous: BTreeSet<_> = (0..width).collect(); |
| 92 | + let mut current: BTreeSet<_> = BTreeSet::new(); |
| 93 | + |
| 94 | + // If a button can increment a joltage counter then it get a coefficent of 1, otherwise zero. |
| 95 | + // Using the first example machine and labelling the button a..f from left to right: |
| 96 | + // |
| 97 | + // * [.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7} |
| 98 | + // * d + f = 3 |
| 99 | + // * b + f = 5 |
| 100 | + // * c + d + e = 4 |
| 101 | + // * a + b + d = 7 |
| 102 | + for (row, &joltage) in joltages.iter().enumerate() { |
| 103 | + equations[row][width] = joltage; |
| 104 | + |
| 105 | + for col in 0..width { |
| 106 | + if buttons[col] & (1 << row) != 0 { |
| 107 | + equations[row][col] = 1; |
| 108 | + limit[col] = limit[col].min(joltage); |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + // Gaussian elimination to reduce the matrix to row echelon form. For example the previous |
| 114 | + // equations form a matrix: |
| 115 | + // |
| 116 | + // [ 0 0 0 1 0 1 | 3 ] |
| 117 | + // [ 0 1 0 0 0 1 | 4 ] |
| 118 | + // [ 0 0 1 1 1 0 | 5 ] |
| 119 | + // [ 1 0 0 1 0 0 | 7 ] |
| 120 | + while previous != current { |
| 121 | + previous = current; |
| 122 | + current = (0..width).collect(); |
| 123 | + |
| 124 | + let mut pivot_row = 0; |
| 125 | + let mut pivot_col = 0; |
| 126 | + |
| 127 | + while pivot_row < height && pivot_col < width { |
| 128 | + let Some(found) = (pivot_row..height).find(|&row| { |
| 129 | + let coefficient = equations[row][pivot_col]; |
| 130 | + coefficient != 0 && equations[row].iter().all(|c| c % coefficient == 0) |
| 131 | + }) else { |
| 132 | + pivot_col += 1; |
| 133 | + continue; |
| 134 | + }; |
| 135 | + |
| 136 | + equations.swap(pivot_row, found); |
| 137 | + let coefficient = equations[pivot_row][pivot_col]; |
| 138 | + equations[pivot_row].iter_mut().for_each(|c| *c /= coefficient); |
| 139 | + |
| 140 | + for row in 0..height { |
| 141 | + if row != pivot_row { |
| 142 | + let coefficient = equations[row][pivot_col]; |
| 143 | + let [from, to] = equations.get_disjoint_mut([pivot_row, row]).unwrap(); |
| 144 | + from.iter().zip(to).for_each(|(f, t)| *t -= coefficient * f); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + current.remove(&pivot_col); |
| 149 | + pivot_row += 1; |
| 150 | + pivot_col += 1; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + if current.is_empty() { |
| 155 | + return (0..height).map(|row| equations[row][width]).sum(); |
| 156 | + } |
| 157 | + |
| 158 | + // Brute force search over the free variables. |
| 159 | + let free = current.len(); |
| 160 | + let fixed = width - current.len(); |
| 161 | + let presses = (0..fixed).map(|row| equations[row][width]).sum::<i32>(); |
| 162 | + let mut cost = vec![0; free]; |
| 163 | + let mut coefficients = vec![vec![0; height]; free]; |
| 164 | + let mut rhs = vec![vec![0; height]; free]; |
| 165 | + let mut ordered_limit: Vec<_> = vec![0; free]; |
| 166 | + |
| 167 | + for (to, &from) in current.iter().enumerate() { |
| 168 | + cost[to] = 1 - (0..fixed).map(|row| equations[row][from]).sum::<i32>(); |
| 169 | + ordered_limit[to] = limit[from]; |
| 170 | + |
| 171 | + for row in 0..height { |
| 172 | + coefficients[to][row] = equations[row][from]; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + for row in 0..height { |
| 177 | + rhs[0][row] = equations[row][width]; |
| 178 | + } |
| 179 | + |
| 180 | + recurse(&cost, &ordered_limit, &coefficients, &mut rhs, fixed, presses, 0).unwrap() |
| 181 | +} |
| 182 | + |
| 183 | +fn recurse( |
| 184 | + cost: &[i32], |
| 185 | + limit: &[i32], |
| 186 | + coefficients: &[Vec<i32>], |
| 187 | + rhs: &mut [Vec<i32>], |
| 188 | + fixed: usize, |
| 189 | + presses: i32, |
| 190 | + depth: usize, |
| 191 | +) -> Option<i32> { |
| 192 | + let height = rhs[depth].len(); |
| 193 | + |
| 194 | + if depth == coefficients.len() - 1 { |
| 195 | + // For the last free variables, we can use the remaining inequalities (and possibily and |
| 196 | + // equalities to find the answer immediately) without needed another search. This |
| 197 | + // reduces the dimensions of the search space by one. |
| 198 | + let mut lower = 0; |
| 199 | + let mut upper = limit[depth]; |
| 200 | + |
| 201 | + // Check inequalites |
| 202 | + for (&coef, &rhs) in coefficients[depth].iter().zip(&rhs[depth]) { |
| 203 | + if rhs >= 0 { |
| 204 | + if coef > 0 { |
| 205 | + upper = upper.min(rhs / coef); |
| 206 | + } |
| 207 | + } else if coef < 0 { |
| 208 | + let floor = (rhs + coef + 1) / coef; |
| 209 | + lower = lower.max(floor); |
| 210 | + } else { |
| 211 | + upper = -1; |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + // Check equalities (if they exist) |
| 216 | + for row in fixed..height { |
| 217 | + let c = coefficients[depth][row]; |
| 218 | + let r = rhs[depth][row]; |
| 219 | + |
| 220 | + if c != 0 { |
| 221 | + if r % c == 0 { |
| 222 | + upper = upper.min(r / c); |
| 223 | + lower = lower.max(r / c); |
| 224 | + } else { |
| 225 | + upper = -1; |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + let presses = presses + cost[depth] * if cost[depth] >= 0 { lower } else { upper }; |
| 231 | + (lower <= upper).then_some(presses) |
| 232 | + } else { |
| 233 | + (0..=limit[depth]) |
| 234 | + .filter_map(|x| { |
| 235 | + let next_presses = presses + x * cost[depth]; |
| 236 | + |
| 237 | + for row in 0..height { |
| 238 | + rhs[depth + 1][row] = rhs[depth][row] - x * coefficients[depth][row]; |
| 239 | + } |
| 240 | + |
| 241 | + recurse(cost, limit, coefficients, rhs, fixed, next_presses, depth + 1) |
| 242 | + }) |
| 243 | + .min() |
| 244 | + } |
| 245 | +} |
0 commit comments