+use std::{
+ fs,
+ io::{BufRead, Read},
+ time::Instant,
+};
+
+use clap::Parser;
+use rayon::prelude::*;
+
+#[derive(Parser, Debug)]
+#[command(author = "Greg Burri", version = "1.0", about = "Advent of Code 2024")]
+struct Args {
+ #[arg(index(1), conflicts_with_all(["parallel"]))]
+ day: Option<usize>,
+
+ /// Run all days in parallel.
+ #[arg(short, long)]
+ parallel: bool,
+
+ /// Number of time each day is executed, the average time is displayed.
+ #[arg(short, long, default_value_t = 1)]
+ repeat: u32,
+}
+
+pub fn run(days: Vec<fn(&mut dyn BufRead) -> String>) {
+ let args = Args::parse();
+
+ match args.day {
+ Some(day) => {
+ if day >= 1 && day <= days.len() {
+ do_day(&days, day, args.repeat)
+ } else {
+ println!("Unknown day: {}", day)
+ }
+ }
+ // No argument -> execute all day problems.
+ None => {
+ let now = Instant::now();
+
+ if args.parallel {
+ (1..=days.len())
+ .into_par_iter()
+ .for_each(|d| do_day(&days, d, args.repeat));
+ } else {
+ (1..=days.len()).for_each(|d| do_day(&days, d, args.repeat));
+ }
+
+ println!(
+ "Time to execute all days: {}",
+ format_micros(now.elapsed().as_micros())
+ );
+ }
+ }
+}
+
+fn do_day(days: &[fn(&mut dyn BufRead) -> String], day: usize, repeat: u32) {
+ let filepath = format!("data/day{:02}.input", day);
+ let mut f = fs::File::open(&filepath).unwrap_or_else(|error| {
+ println!(
+ "Cannot find file {}. Did you place the input files in the 'data' directory?\nError:{}",
+ filepath, error
+ );
+ panic!();
+ });
+
+ // We read the whole file to avoid measuring I/O time.
+ let mut buffer = Vec::new();
+ f.read_to_end(&mut buffer).unwrap();
+
+ let now = Instant::now();
+ for i in 0..repeat {
+ let result = days[day - 1](&mut buffer.as_slice());
+ if i == repeat - 1 {
+ println!(
+ "Result of day {:02}: {} (time: {})",
+ day,
+ result,
+ format_micros(now.elapsed().as_micros() / repeat as u128)
+ );
+ }
+ }
+}
+
+fn format_micros(t: u128) -> String {
+ if t < 10_000 {
+ format!("{} μs", t)
+ } else if t < 10_000_000u128 {
+ format!("{:.2} ms", t as f64 / 1e3f64)
+ } else {
+ format!("{:.2} s", t as f64 / 1e6f64)
+ }