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 #[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}