aocoracle 0.1.2

Universal solver for advent of code problems
Documentation
use std::fs;

use hashbrown::{HashMap, HashSet};
use itertools::Itertools;

type AnyError = Box<dyn std::error::Error>;
type Key = HashMap<String, usize>;

fn _display(line: &str) -> Vec<Vec<String>> {
    line.split('|')
        .map(|vs| vs.split_whitespace().map(|p| p.chars().collect()).collect())
        .collect()
}

fn _displays(text: &str) -> Vec<Vec<Vec<String>>> {
    text.lines().map(_display).collect()
}

fn _key(patterns: &[String]) -> Key {
    let by_length: HashMap<usize, Vec<HashSet<char>>> = patterns
        .iter()
        .map(|v| (v.len(), v.chars().collect()))
        .into_group_map()
        .drain()
        .collect();

    let one = by_length.get(&2).unwrap().iter().exactly_one().unwrap();
    let four = by_length.get(&4).unwrap().iter().exactly_one().unwrap();
    let seven = by_length.get(&3).unwrap().iter().exactly_one().unwrap();
    let eight = by_length.get(&7).unwrap().iter().exactly_one().unwrap();

    let three = by_length
        .get(&5)
        .unwrap()
        .iter()
        .filter(|v| v.is_superset(one))
        .exactly_one()
        .unwrap();
    let six = by_length
        .get(&6)
        .unwrap()
        .iter()
        .filter(|v| !v.is_superset(one))
        .exactly_one()
        .unwrap();
    let b = four.difference(three).exactly_one().unwrap();

    let two = by_length
        .get(&5)
        .unwrap()
        .iter()
        .filter(|v| *v != three && !v.contains(b))
        .exactly_one()
        .unwrap();
    let five = by_length
        .get(&5)
        .unwrap()
        .iter()
        .filter(|v| *v != three && v.contains(b))
        .exactly_one()
        .unwrap();
    let e = six.difference(five).exactly_one().unwrap();

    let zero = by_length
        .get(&6)
        .unwrap()
        .iter()
        .filter(|v| *v != six && v.contains(e))
        .exactly_one()
        .unwrap();
    let nine = by_length
        .get(&6)
        .unwrap()
        .iter()
        .filter(|v| *v != six && !v.contains(e))
        .exactly_one()
        .unwrap();

    vec![zero, one, two, three, four, five, six, seven, eight, nine]
        .iter()
        .enumerate()
        .map(|(i, vs)| (vs.iter().sorted().collect(), i))
        .collect()
}

fn _decoded(digits: &[String], key: Key) -> u32 {
    let mut result = 0;
    digits
        .iter()
        .map(|v| key.get(&v.chars().sorted().collect::<String>()).unwrap())
        .for_each(|d| result = result * 10 + *d as u32);
    result
}

fn _cracked_and_decoded(train: &[String], test: &[String]) -> u32 {
    let key = _key(train);
    _decoded(test, key)
}

pub fn part_1(input: &str) -> Result<String, AnyError> {
    let displays = _displays(input);
    let num_1478 = displays
        .iter()
        .map(|vs| vs.get(1).unwrap())
        .flatten()
        .filter(|v| matches!(v.len(), 2 | 3 | 4 | 7))
        .count();
    Ok(format!("{}", num_1478))
}

pub fn part_2(input: &str) -> Result<String, AnyError> {
    let displays = _displays(input);
    let sum = displays
        .iter()
        .map(|d| _cracked_and_decoded(d.get(0).unwrap(), d.get(1).unwrap()))
        .sum::<u32>();
    Ok(format!("{}", sum))
}

fn _from_file<F, T>(func: F, stem: &str) -> T
where
    F: Fn(&str) -> Result<T, AnyError>,
{
    func(&fs::read_to_string(format!("inputs/08/{}.txt", stem)).unwrap()).unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part_1_works_on_example_s() {
        assert_eq!(_from_file(part_1, "example"), "26");
    }

    #[test]
    fn part_1_works_on_input() {
        assert_eq!(_from_file(part_1, "input"), "470");
    }

    #[test]
    fn part_2_works_on_example_l() {
        assert_eq!(_from_file(part_2, "example"), "61229");
    }

    #[test]
    fn part_2_works_on_input() {
        assert_eq!(_from_file(part_2, "input"), "989396");
    }
}