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}