advent_of_code_ocr/
lib.rs

1use std::collections::HashMap;
2
3use itertools::Itertools;
4use phf::phf_map;
5
6static CHARACTER_MAP: phf::Map<&'static str, char> = phf_map! {
7    ".##.\n#..#\n#..#\n####\n#..#\n#..#" => 'A',
8    "###.\n#..#\n###.\n#..#\n#..#\n###." => 'B',
9    ".##.\n#..#\n#...\n#...\n#..#\n.##." => 'C',
10    "####\n#...\n###.\n#...\n#...\n####" => 'E',
11    "####\n#...\n###.\n#...\n#...\n#..." => 'F',
12    ".##.\n#..#\n#...\n#.##\n#..#\n.###" => 'G',
13    "#..#\n#..#\n####\n#..#\n#..#\n#..#" => 'H',
14    ".###\n..#.\n..#.\n..#.\n..#.\n.###" => 'I',
15    "..##\n...#\n...#\n...#\n#..#\n.##." => 'J',
16    "#..#\n#.#.\n##..\n#.#.\n#.#.\n#..#" => 'K',
17    "#...\n#...\n#...\n#...\n#...\n####" => 'L',
18    ".##.\n#..#\n#..#\n#..#\n#..#\n.##." => 'O',
19    "###.\n#..#\n#..#\n###.\n#...\n#..." => 'P',
20    "###.\n#..#\n#..#\n###.\n#.#.\n#..#" => 'R',
21    ".###\n#...\n#...\n.##.\n...#\n###." => 'S',
22    "#..#\n#..#\n#..#\n#..#\n#..#\n.##." => 'U',
23    "#...\n#...\n.#.#\n..#.\n..#.\n..#." => 'Y',
24    "####\n...#\n..#.\n.#..\n#...\n####" => 'Z',
25};
26
27/// Parse a letter represented in the AoC format.
28///
29/// Example:
30/// ```
31/// # use advent_of_code_ocr::parse_letter;
32/// let letter = "#..#\n#..#\n####\n#..#\n#..#\n#..#";
33///
34/// assert_eq!(parse_letter(letter), Some('H'));
35/// ```
36pub fn parse_letter(letter: &str) -> Option<char> {
37    CHARACTER_MAP.get(letter).copied()
38}
39
40/// Parse a string representing a full screen from a AoC puzzle.
41///
42/// Note: The current version will return all the characters it can parse,
43/// and ignore everyone that it cannot. To check individual characters, use the
44/// `parse_letter` function to parse them and get an `Option<char>`.
45///
46/// Example:
47/// ```
48/// # use advent_of_code_ocr::parse_string_to_letters;
49///
50/// // Input is:
51/// // ####.###....##.###..###..#..#..##..#..#.
52/// // #....#..#....#.#..#.#..#.#.#..#..#.#..#.
53/// // ###..#..#....#.###..#..#.##...#..#.####.
54/// // #....###.....#.#..#.###..#.#..####.#..#.
55/// // #....#....#..#.#..#.#.#..#.#..#..#.#..#.
56/// // ####.#.....##..###..#..#.#..#.#..#.#..#.
57/// let input = "####.###....##.###..###..#..#..##..#..#.\n#....#..#....#.#..#.#..#.#.#..#..#.#..#.\n###..#..#....#.###..#..#.##...#..#.####.\n#....###.....#.#..#.###..#.#..####.#..#.\n#....#....#..#.#..#.#.#..#.#..#..#.#..#.\n####.#.....##..###..#..#.#..#.#..#.#..#.";
58///
59/// assert_eq!(parse_string_to_letters(input), "EPJBRKAH");
60/// ```
61pub fn parse_string_to_letters(s: &str) -> String {
62    split_screen(s)
63        .iter()
64        .filter_map(|x| parse_letter(x.as_str()))
65        .collect()
66}
67
68/// Split a string representing a AoC screen into the section strings for the
69/// individual characters. This assumes every character is 4 characters wide and a
70/// single column is used to split the individual letters.
71///
72/// This will split the string:
73/// ```text
74/// ####.###....##.###..###..#..#..##..#..#.
75/// #....#..#....#.#..#.#..#.#.#..#..#.#..#.
76/// ###..#..#....#.###..#..#.##...#..#.####.
77/// #....###.....#.#..#.###..#.#..####.#..#.
78/// #....#....#..#.#..#.#.#..#.#..#..#.#..#.
79/// ####.#.....##..###..#..#.#..#.#..#.#..#.
80/// ```
81///
82/// into the vector of
83///
84/// ```text
85/// ####    ###.    ..##    ###.    ###.    #..#    .##.    #..#
86/// #...    #..#    ...#    #..#    #..#    #.#.    #..#    #..#
87/// ###.    #..#    ...#    ###.    #..#    ##..    #..#    ####
88/// #...    ###.    ...#    #..#    ###.    #.#.    ####    #..#
89/// #...    #...    #..#    #..#    #.#.    #.#.    #..#    #..#
90/// ####    #...    .##.    ###.    #..#    #..#    #..#    #..#
91/// ```
92pub fn split_screen(s: &str) -> Vec<String> {
93    s.lines()
94        .fold(HashMap::<usize, Vec<char>>::new(), |letters, line| {
95            line.chars().chunks(5).into_iter().enumerate().fold(
96                letters,
97                |mut letters, (n, chunk)| {
98                    if letters.contains_key(&n) {
99                        letters.get_mut(&n).unwrap().push('\n');
100                        letters.get_mut(&n).unwrap().extend(chunk.take(4));
101                    } else {
102                        letters.entry(n).or_insert_with(|| chunk.take(4).collect());
103                    }
104                    letters
105                },
106            )
107        })
108        .iter()
109        .sorted_by_key(|(n, _)| **n)
110        .map(|(_, v)| v)
111        .map(|x| x.iter().collect::<String>())
112        .collect()
113}
114
115#[cfg(test)]
116mod test {
117    use super::*;
118
119    #[test]
120    fn map_letter() {
121        let a = ".##.
122#..#
123#..#
124####
125#..#
126#..#";
127
128        assert_eq!(parse_letter(a), Some('A'));
129    }
130
131    #[test]
132    fn split_screen_to_sections() {
133        let screen = "####.###....##.###..###..#..#..##..#..#.\n#....#..#....#.#..#.#..#.#.#..#..#.#..#.\n###..#..#....#.###..#..#.##...#..#.####.\n#....###.....#.#..#.###..#.#..####.#..#.\n#....#....#..#.#..#.#.#..#.#..#..#.#..#.\n####.#.....##..###..#..#.#..#.#..#.#..#.";
134
135        let binding = split_screen(screen);
136        let mut it = binding.iter();
137        assert_eq!(
138            it.next(),
139            Some(&"####\n#...\n###.\n#...\n#...\n####".to_string())
140        );
141        assert_eq!(
142            it.next(),
143            Some(&"###.\n#..#\n#..#\n###.\n#...\n#...".to_string())
144        );
145        assert_eq!(
146            it.next(),
147            Some(&"..##\n...#\n...#\n...#\n#..#\n.##.".to_string())
148        );
149        assert_eq!(
150            it.next(),
151            Some(&"###.\n#..#\n###.\n#..#\n#..#\n###.".to_string())
152        );
153        assert_eq!(
154            it.next(),
155            Some(&"###.\n#..#\n#..#\n###.\n#.#.\n#..#".to_string())
156        );
157        assert_eq!(
158            it.next(),
159            Some(&"#..#\n#.#.\n##..\n#.#.\n#.#.\n#..#".to_string())
160        );
161        assert_eq!(
162            it.next(),
163            Some(&".##.\n#..#\n#..#\n####\n#..#\n#..#".to_string())
164        );
165        assert_eq!(
166            it.next(),
167            Some(&"#..#\n#..#\n####\n#..#\n#..#\n#..#".to_string())
168        );
169        assert_eq!(it.next(), None);
170    }
171}