aoclib/
day_08.rs

1use std::fs;
2
3use hashbrown::{HashMap, HashSet};
4use itertools::Itertools;
5
6type AnyError = Box<dyn std::error::Error>;
7type Key = HashMap<String, usize>;
8
9fn _display(line: &str) -> Vec<Vec<String>> {
10    line.split('|')
11        .map(|vs| vs.split_whitespace().map(|p| p.chars().collect()).collect())
12        .collect()
13}
14
15fn _displays(text: &str) -> Vec<Vec<Vec<String>>> {
16    text.lines().map(_display).collect()
17}
18
19fn _key(patterns: &[String]) -> Key {
20    let by_length: HashMap<usize, Vec<HashSet<char>>> = patterns
21        .iter()
22        .map(|v| (v.len(), v.chars().collect()))
23        .into_group_map()
24        .drain()
25        .collect();
26
27    let one = by_length.get(&2).unwrap().iter().exactly_one().unwrap();
28    let four = by_length.get(&4).unwrap().iter().exactly_one().unwrap();
29    let seven = by_length.get(&3).unwrap().iter().exactly_one().unwrap();
30    let eight = by_length.get(&7).unwrap().iter().exactly_one().unwrap();
31
32    let three = by_length
33        .get(&5)
34        .unwrap()
35        .iter()
36        .filter(|v| v.is_superset(one))
37        .exactly_one()
38        .unwrap();
39    let six = by_length
40        .get(&6)
41        .unwrap()
42        .iter()
43        .filter(|v| !v.is_superset(one))
44        .exactly_one()
45        .unwrap();
46    let b = four.difference(three).exactly_one().unwrap();
47
48    let two = by_length
49        .get(&5)
50        .unwrap()
51        .iter()
52        .filter(|v| *v != three && !v.contains(b))
53        .exactly_one()
54        .unwrap();
55    let five = by_length
56        .get(&5)
57        .unwrap()
58        .iter()
59        .filter(|v| *v != three && v.contains(b))
60        .exactly_one()
61        .unwrap();
62    let e = six.difference(five).exactly_one().unwrap();
63
64    let zero = by_length
65        .get(&6)
66        .unwrap()
67        .iter()
68        .filter(|v| *v != six && v.contains(e))
69        .exactly_one()
70        .unwrap();
71    let nine = by_length
72        .get(&6)
73        .unwrap()
74        .iter()
75        .filter(|v| *v != six && !v.contains(e))
76        .exactly_one()
77        .unwrap();
78
79    vec![zero, one, two, three, four, five, six, seven, eight, nine]
80        .iter()
81        .enumerate()
82        .map(|(i, vs)| (vs.iter().sorted().collect(), i))
83        .collect()
84}
85
86fn _decoded(digits: &[String], key: Key) -> u32 {
87    let mut result = 0;
88    digits
89        .iter()
90        .map(|v| key.get(&v.chars().sorted().collect::<String>()).unwrap())
91        .for_each(|d| result = result * 10 + *d as u32);
92    result
93}
94
95fn _cracked_and_decoded(train: &[String], test: &[String]) -> u32 {
96    let key = _key(train);
97    _decoded(test, key)
98}
99
100pub fn part_1(input: &str) -> Result<String, AnyError> {
101    let displays = _displays(input);
102    let num_1478 = displays
103        .iter()
104        .map(|vs| vs.get(1).unwrap())
105        .flatten()
106        .filter(|v| matches!(v.len(), 2 | 3 | 4 | 7))
107        .count();
108    Ok(format!("{}", num_1478))
109}
110
111pub fn part_2(input: &str) -> Result<String, AnyError> {
112    let displays = _displays(input);
113    let sum = displays
114        .iter()
115        .map(|d| _cracked_and_decoded(d.get(0).unwrap(), d.get(1).unwrap()))
116        .sum::<u32>();
117    Ok(format!("{}", sum))
118}
119
120fn _from_file<F, T>(func: F, stem: &str) -> T
121where
122    F: Fn(&str) -> Result<T, AnyError>,
123{
124    func(&fs::read_to_string(format!("inputs/08/{}.txt", stem)).unwrap()).unwrap()
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn part_1_works_on_example_s() {
133        assert_eq!(_from_file(part_1, "example"), "26");
134    }
135
136    #[test]
137    fn part_1_works_on_input() {
138        assert_eq!(_from_file(part_1, "input"), "470");
139    }
140
141    #[test]
142    fn part_2_works_on_example_l() {
143        assert_eq!(_from_file(part_2, "example"), "61229");
144    }
145
146    #[test]
147    fn part_2_works_on_input() {
148        assert_eq!(_from_file(part_2, "input"), "989396");
149    }
150}