|
| 1 | +//! # No Such Thing as Too Much |
| 2 | +//! |
| 3 | +//! Given `n` items the number of possible subsets is `2ⁿ`. We could brute force through each |
| 4 | +//! subset by iterating from 0 to 2ⁿ using the binary bits to indicate if a container is present. |
| 5 | +//! This will work but is a little slow as there are 20 containers, giving 2²⁰ = 1048576 |
| 6 | +//! combinations to check. |
| 7 | +//! |
| 8 | +//! We speed things up by noticing that some containers are the same size, so the total number |
| 9 | +//! of combinations is fewer. For example if there are 3 containers of size `x` that is only |
| 10 | +//! 4 possibilities rather than 8. |
| 11 | +//! |
| 12 | +//! We have to multiply the result by [nCr](https://en.wikipedia.org/wiki/Combination) |
| 13 | +//! or the number of ways of choosing `r` items from `n`. For example if 2 containers out of 3 |
| 14 | +//! are used in a solution then there are 3 ways of selecting the 2 containers. This solution |
| 15 | +//! hardcodes nCr tables for `n` up to 4. |
| 16 | +//! |
| 17 | +//! As an optimization containers are ordered from largest to smallest so that the |
| 18 | +//! recursive checking can exit as early as possible if we exceed 150 litres. |
| 19 | +use crate::util::parse::*; |
| 20 | +use std::collections::BTreeMap; |
| 21 | + |
| 22 | +/// nCr for `n` from 0 to 4 inclusive. |
| 23 | +const NCR: [[u32; 5]; 5] = |
| 24 | + [[1, 0, 0, 0, 0], [1, 1, 0, 0, 0], [1, 2, 1, 0, 0], [1, 3, 3, 1, 0], [1, 4, 6, 4, 1]]; |
| 25 | + |
| 26 | +struct State { |
| 27 | + size: Vec<u32>, |
| 28 | + freq: Vec<usize>, |
| 29 | + result: Vec<u32>, |
| 30 | +} |
| 31 | + |
| 32 | +pub fn parse(input: &str) -> Vec<u32> { |
| 33 | + // Collect size and frequency of each container. |
| 34 | + let mut containers = BTreeMap::new(); |
| 35 | + |
| 36 | + for size in input.iter_unsigned() { |
| 37 | + containers.entry(size).and_modify(|e| *e += 1).or_insert(1); |
| 38 | + } |
| 39 | + |
| 40 | + // Convenience struct to group size and frequency of each container, plus the number of |
| 41 | + // combinations grouped by total number of containers. |
| 42 | + let mut state = State { |
| 43 | + size: containers.keys().copied().collect(), |
| 44 | + freq: containers.values().copied().collect(), |
| 45 | + result: vec![0; containers.len()], |
| 46 | + }; |
| 47 | + |
| 48 | + // As an optimization order largest containers first so that we can exit early if the total |
| 49 | + // size is greater than 150. |
| 50 | + state.size.reverse(); |
| 51 | + state.freq.reverse(); |
| 52 | + |
| 53 | + combinations(&mut state, 0, 0, 0, 1); |
| 54 | + state.result |
| 55 | +} |
| 56 | + |
| 57 | +/// We only care about the total combinations, so sum the entire vec. |
| 58 | +pub fn part1(input: &[u32]) -> u32 { |
| 59 | + input.iter().sum() |
| 60 | +} |
| 61 | + |
| 62 | +/// We want the number of combination with the fewest containers, so find first non-zero value. |
| 63 | +pub fn part2(input: &[u32]) -> u32 { |
| 64 | + *input.iter().find(|&&n| n > 0).unwrap() |
| 65 | +} |
| 66 | + |
| 67 | +/// Recursively try every possible combination, returning early if the size exceeds 150 litres. |
| 68 | +/// |
| 69 | +/// `state`: Convenience struct to reduce parameters |
| 70 | +/// `index`: Current container |
| 71 | +/// `containers`: Number of containers used so far |
| 72 | +/// `litres`: How many litres of eggnog stored so far |
| 73 | +/// `factor`: The total different number of ways of selecting previous containers |
| 74 | +fn combinations(state: &mut State, index: usize, containers: usize, litres: u32, factor: u32) { |
| 75 | + let n = state.freq[index]; |
| 76 | + let mut next = litres; |
| 77 | + |
| 78 | + for r in 0..(n + 1) { |
| 79 | + if next < 150 { |
| 80 | + if index < state.size.len() - 1 { |
| 81 | + combinations(state, index + 1, containers + r, next, factor * NCR[n][r]); |
| 82 | + } |
| 83 | + } else { |
| 84 | + if next == 150 { |
| 85 | + state.result[containers + r] += factor * NCR[n][r]; |
| 86 | + } |
| 87 | + break; |
| 88 | + } |
| 89 | + next += state.size[index]; |
| 90 | + } |
| 91 | +} |
0 commit comments