aoclib/
aoclib.rs

1use std::collections::{BTreeMap, HashMap};
2
3use structopt::StructOpt;
4
5mod day_01;
6mod day_02;
7mod day_03;
8mod day_04;
9mod day_05;
10mod day_06;
11mod day_07;
12pub mod day_08;
13pub mod day_20;
14pub mod day_21;
15pub mod day_22;
16pub mod day_23;
17pub mod day_24;
18pub mod day_25;
19
20#[derive(StructOpt)]
21pub struct Cli {
22    #[structopt(long)]
23    day: Option<u32>,
24    #[structopt(long)]
25    part: Option<u32>,
26}
27
28const NUM_DAY: u32 = 7;
29
30type AnyError = Box<dyn std::error::Error>;
31type Solver = dyn Fn(&str) -> Result<String, AnyError>;
32
33fn _candidates(args: &Cli) -> Result<BTreeMap<(u32, u32), &Solver>, AnyError> {
34    let mut functions: HashMap<(u32, u32), &Solver> = HashMap::new();
35    functions.insert((1, 1), &day_01::part_1);
36    functions.insert((1, 2), &day_01::part_2);
37    functions.insert((2, 1), &day_02::part_1);
38    functions.insert((2, 2), &day_02::part_2);
39    functions.insert((3, 1), &day_03::part_1);
40    functions.insert((3, 2), &day_03::part_2);
41    functions.insert((4, 1), &day_04::part_1);
42    functions.insert((4, 2), &day_04::part_2);
43    functions.insert((5, 1), &day_05::part_1);
44    functions.insert((5, 2), &day_05::part_2);
45    functions.insert((6, 1), &day_06::part_1);
46    functions.insert((6, 2), &day_06::part_2);
47    functions.insert((7, 1), &day_07::part_1);
48    functions.insert((7, 2), &day_07::part_2);
49    let functions = functions;
50
51    let mut result = BTreeMap::new();
52    let parts: Vec<u32> = match args.part {
53        None => 1..=2,
54        Some(1) => 1..=1,
55        Some(2) => 2..=2,
56        _ => return Err("Invalid part".into()),
57    }
58    .collect();
59
60    let days = match args.day {
61        None => 1..=NUM_DAY,
62        Some(day) if (1..=NUM_DAY).contains(&day) => day..=day,
63        _ => return Err("Invalid day".into()),
64    };
65
66    for day in days {
67        for part in &parts {
68            result.insert((day, *part), functions[&(day, *part)]);
69        }
70    }
71
72    Ok(result)
73}
74
75pub fn helper(args: &Cli, text: &str) -> Result<Vec<String>, AnyError> {
76    let candidates: BTreeMap<(u32, u32), &Solver> = _candidates(args)?;
77    if candidates.is_empty() {
78        return Err("Invalid day and part".into());
79    }
80
81    let mut result = Vec::new();
82    for ((day, part), func) in candidates.iter() {
83        log::debug!("Trying day {} part {}", day, part);
84        match func(text) {
85            Ok(output) => result.push(output),
86            Err(error) => match args.day {
87                None => log::debug!("{}", error),
88                Some(_) => return Err(error),
89            },
90        }
91    }
92    Ok(result)
93}
94
95#[cfg(test)]
96mod tests {
97    use std::fs;
98
99    use super::*;
100
101    const STEMS: [&str; 2] = ["example", "input"];
102
103    #[test]
104    fn specific_day_returns_2_solutions_on_right_input() {
105        for day in 1..=NUM_DAY {
106            let args = Cli {
107                day: Some(day),
108                part: None,
109            };
110            let input_day = day;
111            for stem in STEMS {
112                let stdin =
113                    fs::read_to_string(format!("inputs/{:02}/{}.txt", input_day, stem)).unwrap();
114                println!("day:{} input_day:{} stem:{}", day, input_day, stem);
115                assert_eq!(helper(&args, &stdin).unwrap().len(), 2);
116            }
117        }
118    }
119
120    // It is possible to construct input for day 3 that would trigger day 1, would be
121    // nice to eliminate this if it is even possible.
122    #[test]
123    fn specific_part_returns_1_solution_on_right_input() {
124        for part in 1..=2 {
125            let args = Cli {
126                day: None,
127                part: Some(part),
128            };
129            for input_day in 1..=NUM_DAY {
130                for stem in STEMS {
131                    let stdin = fs::read_to_string(format!("inputs/{:02}/{}.txt", input_day, stem))
132                        .unwrap();
133                    println!("day:* input_day:{} stem:{}", input_day, stem);
134                    assert_eq!(helper(&args, &stdin).unwrap().len(), 1);
135                }
136            }
137        }
138    }
139
140    #[test]
141    #[ignore]
142    fn specific_day_returns_error_on_wrong_input() {
143        for day in 1..=NUM_DAY {
144            let args = Cli {
145                day: Some(day),
146                part: None,
147            };
148            for input_day in (1..=NUM_DAY).filter(|d| *d != day) {
149                for stem in STEMS {
150                    let stdin = fs::read_to_string(format!("inputs/{:02}/{}.txt", input_day, stem))
151                        .unwrap();
152                    println!("day:{} input_day:{} stem:{}", day, input_day, stem);
153                    helper(&args, &stdin).unwrap().len();
154                }
155            }
156        }
157    }
158}