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