- Use of advent_of_code_common crate master
authorGreg Burri <greg.burri@gmail.com>
Tue, 10 Dec 2024 22:16:17 +0000 (23:16 +0100)
committerGreg Burri <greg.burri@gmail.com>
Tue, 10 Dec 2024 22:16:17 +0000 (23:16 +0100)
- Replace HashMap and HashSet by FxHashMap and FxHashSet (better performances)

15 files changed:
.gitmodules [new file with mode: 0644]
Cargo.toml
advent_of_code_common [new submodule]
src/common.rs
src/day03.rs
src/day06.rs
src/day10.rs
src/day11.rs
src/day12.rs
src/day14.rs
src/day15.rs
src/day17.rs
src/day18.rs
src/days.rs [new file with mode: 0644]
src/main.rs

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..4413612
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "advent_of_code_common"]
+       path = advent_of_code_common
+       url = ssh://gitolite3@gburri.org:9851/advent_of_code_common.git
index 410796d..db0f76e 100644 (file)
@@ -7,8 +7,17 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-itertools = "0.10"
+advent_of_code_common = { path = "advent_of_code_common" }
+
+itertools = "0.13"
 threadpool = "1.8"
 regex = "1"
 num = "0.4"
-num_enum = "0.5"
\ No newline at end of file
+num_enum = "0.7"
+rustc-hash = "2.1"
+
+[profile.release]
+opt-level = 3
+lto = true
+codegen-units = 1
+debug = true      # Needed by 'cargo flamegraph' when profiling.
diff --git a/advent_of_code_common b/advent_of_code_common
new file mode 160000 (submodule)
index 0000000..b9fa090
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit b9fa0908044042af2ca3dd66281b58afd289b4e4
index ab6774b..c706ad7 100644 (file)
@@ -1,13 +1,18 @@
-use std::{fs, path::Path, str::FromStr};\r
+use std::{\r
+    io::{read_to_string, BufRead},\r
+    str::FromStr,\r
+};\r
 \r
-pub fn read_list_of_numbers<P, T>(file: P, sep: &str) -> Vec<T>\r
+pub fn read_list_of_numbers<T>(reader: &mut dyn BufRead, sep: &str) -> Vec<T>\r
 where\r
-    P: AsRef<Path>,\r
     T: FromStr,\r
-    T::Err: std::fmt::Debug\r
-\r
+    T::Err: std::fmt::Debug,\r
 {\r
-    fs::read_to_string(file).unwrap().split(sep).map(|line| line.trim().parse::<T>().unwrap()).collect()\r
+    read_to_string(reader)\r
+        .unwrap()\r
+        .split(sep)\r
+        .map(|line| line.trim().parse::<T>().unwrap())\r
+        .collect()\r
 }\r
 \r
 pub fn layer_to_printable_string(layer: &[u8], width: usize) -> String {\r
@@ -15,15 +20,17 @@ pub fn layer_to_printable_string(layer: &[u8], width: usize) -> String {
     let mut i = 0;\r
 \r
     loop {\r
-        for _ in 0 .. width {\r
+        for _ in 0..width {\r
             if layer[i] == 0 {\r
                 result += " ";\r
             } else {\r
                 result += "█";\r
             }\r
             i += 1;\r
-            if i >= layer.len() { return result }\r
+            if i >= layer.len() {\r
+                return result;\r
+            }\r
         }\r
         result += "\n";\r
     }\r
-}
\ No newline at end of file
+}\r
index f6f16b8..a2cb697 100644 (file)
@@ -1,7 +1,6 @@
-use std::{\r
-    collections::{HashMap, HashSet},\r
-    iter::{FromIterator, Iterator},\r
-};\r
+use std::iter::{FromIterator, Iterator};\r
+\r
+use rustc_hash::{FxHashMap, FxHashSet};\r
 \r
 pub fn split_movements(movements: &str) -> Vec<&str> {\r
     movements.split(',').collect()\r
@@ -43,15 +42,15 @@ fn positions(wire: &[&str]) -> Vec<(i32, i32)> {
 }\r
 \r
 pub fn manhattan_distance_from_cross_to_port(wire1: &[&str], wire2: &[&str]) -> i32 {\r
-    let positions_wire1: HashSet<(i32, i32)> = HashSet::from_iter(positions(wire1));\r
-    let positions_wire2: HashSet<(i32, i32)> = HashSet::from_iter(positions(wire2));\r
-    let cross: HashSet<_> = positions_wire1.intersection(&positions_wire2).collect();\r
+    let positions_wire1: FxHashSet<(i32, i32)> = FxHashSet::from_iter(positions(wire1));\r
+    let positions_wire2: FxHashSet<(i32, i32)> = FxHashSet::from_iter(positions(wire2));\r
+    let cross: FxHashSet<_> = positions_wire1.intersection(&positions_wire2).collect();\r
     cross.iter().map(|(x, y)| x.abs() + y.abs()).min().unwrap()\r
 }\r
 \r
 pub fn first_cross_sum_of_lengths(wire1: &[&str], wire2: &[&str]) -> usize {\r
     let positions_wire1 = positions(wire1);\r
-    let positions_wire1_indexed: HashMap<&(i32, i32), usize> = HashMap::from_iter(\r
+    let positions_wire1_indexed: FxHashMap<&(i32, i32), usize> = FxHashMap::from_iter(\r
         positions_wire1\r
             .iter()\r
             .enumerate()\r
index 77ae402..e4a02d1 100644 (file)
@@ -1,10 +1,12 @@
-use std::{cmp, collections::HashMap};
+use std::cmp;
+
+use rustc_hash::FxHashMap;
 
 // All planets indexing their parent (planet -> parent).
-type Orbits = HashMap<String, String>;
+type Orbits = FxHashMap<String, String>;
 
 pub fn build_orbits(orbits_str: &[&str]) -> Orbits {
-    let mut orbits = Orbits::new();
+    let mut orbits = Orbits::default();
     for orbit in orbits_str {
         let planets: Vec<&str> = orbit.trim().split(')').collect();
         orbits.insert(String::from(planets[1]), String::from(planets[0]));
index 799b0a8..3539e46 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::{HashMap, HashSet};
+use rustc_hash::{FxHashMap, FxHashSet};
 
 pub fn read_map(raw: &str) -> Vec<(i32, i32)> {
     let lines: Vec<&str> = raw.lines().map(|l| l.trim()).collect();
@@ -28,7 +28,7 @@ pub fn find_best_location(map: &[(i32, i32)]) -> (usize, (i32, i32)) {
     let mut best_nb_observable_asteroid = 0;
     let (mut best_x, mut best_y) = (0, 0);
     for (x1, y1) in map {
-        let mut angles = HashSet::<i64>::new();
+        let mut angles = FxHashSet::<i64>::default();
         for (x2, y2) in map {
             angles.insert(angle(*x1, *y1, *x2, *y2));
         }
@@ -50,7 +50,7 @@ pub fn location_nth_vaporized_asteroid(
     n: usize,
 ) -> (i32, i32) {
     // Angle -> [(position, distance)].
-    let mut asteroids = HashMap::<i64, PositionsAndDistances>::new();
+    let mut asteroids = FxHashMap::<i64, PositionsAndDistances>::default();
 
     let (x1, y1) = pos;
     for (x2, y2) in map {
index 822e218..6da63be 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::HashMap;\r
+use rustc_hash::FxHashMap;\r
 \r
 use super::intcode;\r
 \r
@@ -11,7 +11,7 @@ struct Robot {
     next_command: NextCommand,\r
     current_pos: (i32, i32),\r
     current_dir: i32, // 0: up, 1: right, 2: down, 3: left.\r
-    panels: HashMap<(i32, i32), i64>,\r
+    panels: FxHashMap<(i32, i32), i64>,\r
 }\r
 \r
 impl Robot {\r
@@ -20,7 +20,7 @@ impl Robot {
             next_command: NextCommand::Paint,\r
             current_pos: (0, 0),\r
             current_dir: 0,\r
-            panels: HashMap::new(),\r
+            panels: FxHashMap::default(),\r
         }\r
     }\r
 }\r
@@ -53,7 +53,7 @@ impl intcode::IO for Robot {
     }\r
 }\r
 \r
-pub fn run_robot(code: &[i64], initial_value: i64) -> HashMap<(i32, i32), i64> {\r
+pub fn run_robot(code: &[i64], initial_value: i64) -> FxHashMap<(i32, i32), i64> {\r
     let mut robot = Robot::new();\r
     if initial_value != 0 {\r
         robot.panels.insert((0, 0), initial_value);\r
@@ -63,7 +63,7 @@ pub fn run_robot(code: &[i64], initial_value: i64) -> HashMap<(i32, i32), i64> {
     robot.panels\r
 }\r
 \r
-pub fn panels_to_layer(panels: &HashMap<(i32, i32), i64>) -> (Vec<u8>, usize) {\r
+pub fn panels_to_layer(panels: &FxHashMap<(i32, i32), i64>) -> (Vec<u8>, usize) {\r
     let coordinates: Vec<&(i32, i32)> = panels.keys().collect();\r
     let min_x = coordinates.iter().min_by_key(|(x, _)| x).unwrap().0;\r
     let max_x = coordinates.iter().max_by_key(|(x, _)| x).unwrap().0;\r
index 34aefac..17bdedb 100644 (file)
@@ -9,11 +9,9 @@ pub struct Vector3D {
 
 impl AddAssign for Vector3D {
     fn add_assign(&mut self, other: Self) {
-        *self = Self {
-            x: self.x + other.x,
-            y: self.y + other.y,
-            z: self.z + other.z,
-        };
+        self.x += other.x;
+        self.y += other.y;
+        self.z += other.z;
     }
 }
 
index e921cbd..313216a 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use rustc_hash::FxHashMap;
 
 #[derive(Debug)]
 pub struct Chemical {
@@ -6,7 +6,7 @@ pub struct Chemical {
     name: String,
 }
 
-type Reactions = HashMap<String, (i64, Vec<Chemical>)>;
+type Reactions = FxHashMap<String, (i64, Vec<Chemical>)>;
 
 fn parse_chemical(input: &str) -> Chemical {
     let quantity_and_name: Vec<&str> = input.trim().split(' ').collect();
@@ -17,7 +17,7 @@ fn parse_chemical(input: &str) -> Chemical {
 }
 
 pub fn parse(input: &str) -> Reactions {
-    let mut result = Reactions::new();
+    let mut result = Reactions::default();
     for line in input.lines() {
         let reaction: Vec<&str> = line.split("=>").collect();
         let input_chemicals: Vec<Chemical> = reaction[0].split(',').map(parse_chemical).collect();
@@ -31,19 +31,19 @@ pub fn parse(input: &str) -> Reactions {
 }
 
 pub fn ore_needed_per_fuel(reactions: &Reactions) -> i64 {
-    let mut remainders = HashMap::new();
+    let mut remainders = FxHashMap::default();
     ore_needed(reactions, 1, &mut remainders)
 }
 
 fn ore_needed(
     reactions: &Reactions,
     fuel_quantity: i64,
-    remainders: &mut HashMap<String, i64>,
+    remainders: &mut FxHashMap<String, i64>,
 ) -> i64 {
     fn needed(
         reactions: &Reactions,
         chemicals: &[Chemical],
-        remainders: &mut HashMap<String, i64>,
+        remainders: &mut FxHashMap<String, i64>,
     ) -> i64 {
         chemicals.iter().fold(0, |sum, chemical| {
             let quantity_needed = match remainders.get(&chemical.name) {
@@ -111,7 +111,7 @@ fn ore_needed(
 pub fn fuel_produced(reactions: &Reactions, ore: i64, ore_per_fuel: i64) -> i64 {
     let mut ore_available = ore;
     let mut fuel_produced = 0;
-    let mut remainders = HashMap::new();
+    let mut remainders = FxHashMap::default();
 
     loop {
         let fuel = 1.max(ore_available / ore_per_fuel); // Approximate the fuel we can produce.
index b2791e6..df45120 100644 (file)
@@ -1,7 +1,6 @@
-use std::{\r
-    collections::{HashMap, HashSet},\r
-    iter::FromIterator,\r
-};\r
+use std::iter::FromIterator;\r
+\r
+use rustc_hash::{FxHashMap, FxHashSet};\r
 \r
 use super::intcode;\r
 \r
@@ -15,7 +14,7 @@ enum LocationState {
 \r
 #[derive(Clone)]\r
 pub struct DroidTrackingSystem {\r
-    board: HashMap<(i32, i32), LocationState>,\r
+    board: FxHashMap<(i32, i32), LocationState>,\r
     current_path: Vec<(i32, i32)>,\r
     oxygen_location: (i32, i32),\r
     steps_to_oxygen: i32,\r
@@ -25,7 +24,7 @@ pub struct DroidTrackingSystem {
 impl DroidTrackingSystem {\r
     fn new() -> Self {\r
         DroidTrackingSystem {\r
-            board: HashMap::from_iter(vec![((0, 0), LocationState::Visited)].into_iter()),\r
+            board: FxHashMap::from_iter(vec![((0, 0), LocationState::Visited)].into_iter()),\r
             current_path: vec![(0, 0)],\r
             oxygen_location: (0, 0),\r
             steps_to_oxygen: 0,\r
@@ -126,7 +125,7 @@ pub fn nb_of_movement_to_reach_oxygen(code: &[i64]) -> (i32, DroidTrackingSystem
 pub fn time_to_flood_the_area(dts: &DroidTrackingSystem) -> i32 {\r
     let mut dts = dts.clone(); // To be mutable.\r
     dts.current_path = vec![dts.oxygen_location];\r
-    let mut visited: HashSet<(i32, i32)> = HashSet::from_iter(dts.current_path.iter().copied());\r
+    let mut visited: FxHashSet<(i32, i32)> = FxHashSet::from_iter(dts.current_path.iter().copied());\r
     let mut max_length = 0;\r
 \r
     'main: while !dts.current_path.is_empty() {\r
index bdad2bb..08b5825 100644 (file)
@@ -1,4 +1,6 @@
-use std::{collections::HashSet, convert::TryFrom, ops::Range};\r
+use std::{convert::TryFrom, ops::Range};\r
+\r
+use rustc_hash::FxHashSet;\r
 \r
 use itertools::Itertools;\r
 \r
@@ -93,7 +95,7 @@ impl RobotTrackingSystem {
     fn run_through(&mut self) {\r
         let (mut x, mut y) = self.start_position;\r
         let mut dir = self.start_dir;\r
-        let mut visited_locations = HashSet::<(i32, i32)>::new();\r
+        let mut visited_locations = FxHashSet::<(i32, i32)>::default();\r
         visited_locations.insert((x, y));\r
 \r
         let mut last_mov = Movement::Left;\r
index 3bd7819..f43b105 100644 (file)
@@ -1,7 +1,8 @@
-use itertools::Itertools;
-use std::collections::HashSet;
 use std::rc::Rc;
 
+use itertools::Itertools;
+use rustc_hash::FxHashSet;
+
 #[derive(Debug)]
 pub struct Vault {
     tunnels: Vec<Vec<char>>,
@@ -101,7 +102,7 @@ mod v1 {
     pub fn nb_steps_to_collect_all_key(vault: &Vault) -> u32 {
         fn find_keys(from: (i32, i32), parent: Rc<Node>, vault: &Vault) -> Vec<Rc<Node>> {
             let mut to_visit = vec![(from, 1)];
-            let mut visited_positions: HashSet<(i32, i32)> = HashSet::new();
+            let mut visited_positions: FxHashSet<(i32, i32)> = FxHashSet::default();
             let mut reachable_keys = Vec::<Rc<Node>>::new();
 
             if cfg!(debug_assertions) {
@@ -181,7 +182,7 @@ mod v2 {
     #[derive(Debug)]
     struct Path {
         to_visit: Vec<(i32, i32)>,
-        visited: HashSet<(i32, i32)>,
+        visited: FxHashSet<(i32, i32)>,
         keys: Vec<char>,
     }
 
@@ -189,7 +190,7 @@ mod v2 {
         pub fn new(initial_position: (i32, i32)) -> Self {
             Path {
                 to_visit: vec![initial_position],
-                visited: HashSet::new(),
+                visited: FxHashSet::default(),
                 keys: Vec::new(),
             }
         }
@@ -246,7 +247,7 @@ mod v2 {
 
                                 let mut new_path = Path {
                                     to_visit: vec![adjacent],
-                                    visited: HashSet::new(),
+                                    visited: FxHashSet::default(),
                                     keys: path.keys.clone(),
                                 };
                                 new_path.keys.push(c);
diff --git a/src/days.rs b/src/days.rs
new file mode 100644 (file)
index 0000000..82515b1
--- /dev/null
@@ -0,0 +1,191 @@
+use std::io::{read_to_string, BufRead};
+
+use crate::*;
+
+pub fn day01(reader: &mut dyn BufRead) -> String {
+    let masses = common::read_list_of_numbers(reader, "\n");
+    format!(
+        "part1: {}, part2: {}",
+        day01::sum_mass_to_fuel(&masses),
+        day01::sum_mass_to_fuel_2(&masses)
+    )
+}
+
+pub fn day02(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers(reader, ",");
+    format!(
+        "part1: {}, part2: {}",
+        day02::execute_op_code_with_state_fixed(&mut Vec::from(&code[..])),
+        day02::find_noun_and_verb(&code)
+    )
+}
+
+pub fn day03(reader: &mut dyn BufRead) -> String {
+    let file_content = read_to_string(reader).unwrap();
+    let movements: Vec<&str> = file_content.lines().collect();
+    format!(
+        "part1: {}, part2: {}",
+        day03::manhattan_distance_from_cross_to_port(
+            &day03::split_movements(&movements[0]),
+            &day03::split_movements(&movements[1])
+        ),
+        day03::first_cross_sum_of_lengths(
+            &day03::split_movements(&movements[0]),
+            &day03::split_movements(&movements[1])
+        )
+    )
+}
+
+pub fn day04(reader: &mut dyn BufRead) -> String {
+    let raw = read_to_string(reader).unwrap();
+    let (min, max) = day04::parse_range(&raw);
+    format!(
+        "part1: {:?}, part2: {}",
+        day04::nb_passwords_part1(min, max),
+        day04::nb_passwords_part2(min, max)
+    )
+}
+
+pub fn day05(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers(reader, ",");
+    format!(
+        "part1: {:?}, part2: {:?}",
+        intcode::execute_op_code(&code, &[1]),
+        intcode::execute_op_code(&code, &[5])
+    )
+}
+
+pub fn day06(reader: &mut dyn BufRead) -> String {
+    let file_content = read_to_string(reader).unwrap();
+    let lines: Vec<&str> = file_content.lines().collect();
+    let orbits = day06::build_orbits(&lines);
+    format!(
+        "part1: {}, part2: {}",
+        day06::total_direct_and_indirect_orbits(&orbits),
+        day06::nb_orbital_transfers(&orbits, "SAN", "YOU")
+    )
+}
+
+pub fn day07(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers(reader, ",");
+
+    format!(
+        "part1: {}, part2: {}",
+        day07::find_largest_last_thruster_signal(&code),
+        day07::find_largest_last_thruster_signal_with_feedback_loop(&code)
+    )
+}
+
+pub fn day08(reader: &mut dyn BufRead) -> String {
+    let img = read_to_string(reader).unwrap();
+
+    let raw = day08::read_from_string(&img);
+    let layers = day08::decode_image(&raw, 25, 6);
+
+    let layer = day08::layer_with_fewer_0(&layers[..]);
+    let merged = day08::merge_layers(&layers[..]);
+
+    format!(
+        "part1: {}, part2:\n{}",
+        day08::one_digits_times_two_digits(layer),
+        common::layer_to_printable_string(&merged, 25)
+    )
+}
+
+pub fn day09(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers::<i64>(reader, ",");
+
+    format!(
+        "part1: {:?}, part2: {:?}",
+        intcode::execute_op_code(&code, &[1]),
+        intcode::execute_op_code(&code, &[2])
+    )
+}
+
+pub fn day10(reader: &mut dyn BufRead) -> String {
+    let map = day10::read_map(&read_to_string(reader).unwrap());
+    let (n, location) = day10::find_best_location(&map);
+    let (x, y) = day10::location_nth_vaporized_asteroid(location, &map, 200);
+    format!("part1: {}, part2: {}", n, x * 100 + y)
+}
+
+pub fn day11(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers::<i64>(reader, ",");
+    let panels = day11::run_robot(&code, 1);
+    let (layer, width) = day11::panels_to_layer(&panels);
+
+    format!(
+        "part1: {:?}, part2:\n{}",
+        day11::run_robot(&code, 0).len(),
+        common::layer_to_printable_string(&layer, width)
+    )
+}
+
+pub fn day12(reader: &mut dyn BufRead) -> String {
+    let coordinates = day12::parse_positions(&read_to_string(reader).unwrap());
+    format!(
+        "part1: {}, part2: {}",
+        day12::final_energy(&coordinates, 1000),
+        day12::find_same_state(&coordinates)
+    )
+}
+
+pub fn day13(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers::<i64>(reader, ",");
+    let mut modified_code = Vec::from(&code[..]);
+    modified_code[0] = 2;
+    format!(
+        "part1: {}, part2: {}",
+        day13::count_nb_block(&code),
+        day13::final_score(&modified_code)
+    )
+}
+
+pub fn day14(reader: &mut dyn BufRead) -> String {
+    let reactions = day14::parse(&read_to_string(reader).unwrap());
+
+    let ore_per_fuel = day14::ore_needed_per_fuel(&reactions);
+    format!(
+        "part1: {}, part2: {}",
+        ore_per_fuel,
+        day14::fuel_produced(&reactions, 1_000_000_000_000, ore_per_fuel)
+    )
+}
+
+pub fn day15(reader: &mut dyn BufRead) -> String {
+    let code = common::read_list_of_numbers(reader, ",");
+    let (n, dts) = day15::nb_of_movement_to_reach_oxygen(&code);
+    format!(
+        "part1: {}, part2: {}",
+        n,
+        day15::time_to_flood_the_area(&dts)
+    )
+}
+
+pub fn day16(reader: &mut dyn BufRead) -> String {
+    let signal_raw = read_to_string(reader).unwrap();
+    let signal = day16::parse(&signal_raw);
+    let output_part_1 = day16::fft(&signal, &[0, 1, 0, -1], 100, 0, 8, 1);
+    // let output_part_2 = day16::part2(&signal);
+    format!(
+        "part1: {}, part2: {}",
+        day16::digits_as_string(&output_part_1),
+        "<skipped: take too long ~ 20 s>" // day16::digits_as_string(&output_part_2)
+    )
+}
+
+pub fn day17(reader: &mut dyn BufRead) -> String {
+    let mut code = common::read_list_of_numbers(reader, ",");
+    let intersections = day17::scaffold_intersections(&code);
+    code[0] = 2;
+    let dust = day17::collected_dust(&code);
+    format!("part1: {}, part2: {}", intersections, dust)
+}
+
+pub fn day18(reader: &mut dyn BufRead) -> String {
+    let vault_raw = read_to_string(reader).unwrap();
+    let vault = day18::Vault::parse(&vault_raw);
+    let nb_steps = day18::nb_steps_to_collect_all_key(&vault);
+
+    format!("part1: {}, part2: {}", nb_steps, "")
+}
index e23f773..cb3f314 100644 (file)
@@ -1,6 +1,6 @@
-use std::env;
-use std::fs;
-use std::time::Instant;
+use std::io::BufRead;
+
+use advent_of_code_common;
 
 mod common;
 mod day01;
@@ -19,243 +19,38 @@ mod day15;
 mod day16;
 mod day17;
 mod day18;
+mod days;
 mod intcode;
 
-fn day01() -> String {
-    let masses = common::read_list_of_numbers("data/day01.input", "\n");
-    format!(
-        "part1: {}, part2: {}",
-        day01::sum_mass_to_fuel(&masses),
-        day01::sum_mass_to_fuel_2(&masses)
-    )
-}
-
-fn day02() -> String {
-    let code = common::read_list_of_numbers("data/day02.input", ",");
-    format!(
-        "part1: {}, part2: {}",
-        day02::execute_op_code_with_state_fixed(&mut Vec::from(&code[..])),
-        day02::find_noun_and_verb(&code)
-    )
-}
-
-fn day03() -> String {
-    let file_content = fs::read_to_string("data/day03.input").unwrap();
-    let movements: Vec<&str> = file_content.lines().collect();
-    format!(
-        "part1: {}, part2: {}",
-        day03::manhattan_distance_from_cross_to_port(
-            &day03::split_movements(&movements[0]),
-            &day03::split_movements(&movements[1])
-        ),
-        day03::first_cross_sum_of_lengths(
-            &day03::split_movements(&movements[0]),
-            &day03::split_movements(&movements[1])
-        )
-    )
-}
-
-fn day04() -> String {
-    let raw = fs::read_to_string("data/day04.input").unwrap();
-    let (min, max) = day04::parse_range(&raw);
-    format!(
-        "part1: {:?}, part2: {}",
-        day04::nb_passwords_part1(min, max),
-        day04::nb_passwords_part2(min, max)
-    )
-}
-
-fn day05() -> String {
-    let code = common::read_list_of_numbers("data/day05.input", ",");
-    format!(
-        "part1: {:?}, part2: {:?}",
-        intcode::execute_op_code(&code, &[1]),
-        intcode::execute_op_code(&code, &[5])
-    )
-}
-
-fn day06() -> String {
-    let file_content = fs::read_to_string("data/day06.input").unwrap();
-    let lines: Vec<&str> = file_content.lines().collect();
-    let orbits = day06::build_orbits(&lines);
-    format!(
-        "part1: {}, part2: {}",
-        day06::total_direct_and_indirect_orbits(&orbits),
-        day06::nb_orbital_transfers(&orbits, "SAN", "YOU")
-    )
-}
-
-fn day07() -> String {
-    let code = common::read_list_of_numbers("data/day07.input", ",");
-
-    format!(
-        "part1: {}, part2: {}",
-        day07::find_largest_last_thruster_signal(&code),
-        day07::find_largest_last_thruster_signal_with_feedback_loop(&code)
-    )
-}
-
-fn day08() -> String {
-    let img = fs::read_to_string("data/day08.input").unwrap();
-
-    let raw = day08::read_from_string(&img);
-    let layers = day08::decode_image(&raw, 25, 6);
-
-    let layer = day08::layer_with_fewer_0(&layers[..]);
-    let merged = day08::merge_layers(&layers[..]);
-
-    format!(
-        "part1: {}, part2:\n{}",
-        day08::one_digits_times_two_digits(layer),
-        common::layer_to_printable_string(&merged, 25)
-    )
-}
-
-fn day09() -> String {
-    let code = common::read_list_of_numbers::<&str, i64>("data/day09.input", ",");
-
-    format!(
-        "part1: {:?}, part2: {:?}",
-        intcode::execute_op_code(&code, &[1]),
-        intcode::execute_op_code(&code, &[2])
-    )
-}
-
-fn day10() -> String {
-    let map = day10::read_map(&fs::read_to_string("data/day10.input").unwrap());
-    let (n, location) = day10::find_best_location(&map);
-    let (x, y) = day10::location_nth_vaporized_asteroid(location, &map, 200);
-    format!("part1: {}, part2: {}", n, x * 100 + y)
-}
-
-fn day11() -> String {
-    let code = common::read_list_of_numbers::<&str, i64>("data/day11.input", ",");
-    let panels = day11::run_robot(&code, 1);
-    let (layer, width) = day11::panels_to_layer(&panels);
-
-    format!(
-        "part1: {:?}, part2:\n{}",
-        day11::run_robot(&code, 0).len(),
-        common::layer_to_printable_string(&layer, width)
-    )
-}
-
-fn day12() -> String {
-    let coordinates = day12::parse_positions(&fs::read_to_string("data/day12.input").unwrap());
-    format!(
-        "part1: {}, part2: {}",
-        day12::final_energy(&coordinates, 1000),
-        day12::find_same_state(&coordinates)
-    )
-}
-
-fn day13() -> String {
-    let code = common::read_list_of_numbers::<&str, i64>("data/day13.input", ",");
-    let mut modified_code = Vec::from(&code[..]);
-    modified_code[0] = 2;
-    format!(
-        "part1: {}, part2: {}",
-        day13::count_nb_block(&code),
-        day13::final_score(&modified_code)
-    )
-}
-
-fn day14() -> String {
-    let reactions = day14::parse(&fs::read_to_string("data/day14.input").unwrap());
-
-    let ore_per_fuel = day14::ore_needed_per_fuel(&reactions);
-    format!(
-        "part1: {}, part2: {}",
-        ore_per_fuel,
-        day14::fuel_produced(&reactions, 1_000_000_000_000, ore_per_fuel)
-    )
-}
-
-fn day15() -> String {
-    let code = common::read_list_of_numbers("data/day15.input", ",");
-    let (n, dts) = day15::nb_of_movement_to_reach_oxygen(&code);
-    format!(
-        "part1: {}, part2: {}",
-        n,
-        day15::time_to_flood_the_area(&dts)
-    )
-}
-
-fn day16() -> String {
-    let signal_raw = fs::read_to_string("data/day16.input").unwrap();
-    let signal = day16::parse(&signal_raw);
-    let output_part_1 = day16::fft(&signal, &[0, 1, 0, -1], 100, 0, 8, 1);
-    // let output_part_2 = day16::part2(&signal);
-    format!(
-        "part1: {}, part2: {}",
-        day16::digits_as_string(&output_part_1),
-        /*day16::digits_as_string(&output_part_2)*/ "<skipped: take too long ~ 1 min>"
-    )
-}
-
-fn day17() -> String {
-    let mut code = common::read_list_of_numbers("data/day17.input", ",");
-    let intersections = day17::scaffold_intersections(&code);
-    code[0] = 2;
-    let dust = day17::collected_dust(&code);
-    format!("part1: {}, part2: {}", intersections, dust)
-}
-
-fn day18() -> String {
-    let vault_raw = fs::read_to_string("data/day18.input").unwrap();
-    let vault = day18::Vault::parse(&vault_raw);
-    let nb_steps = day18::nb_steps_to_collect_all_key(&vault);
-
-    format!("part1: {}, part2: {}", nb_steps, "")
-}
-
-fn format_micros(t: u128) -> String {
-    if t < 10_000 {
-        format!("{} μs", t)
-    } else if t < 10_000_000u128 {
-        format!("{} ms", t / 1_000u128)
-    } else {
-        format!("{} s", t / 1_000_000u128)
-    }
-}
-
-fn do_day(days: &[fn() -> String], day: usize) {
-    let now = Instant::now();
-    println!(
-        "Result of day {:02}: {} (time: {})",
-        day,
-        days[day - 1](),
-        format_micros(now.elapsed().as_micros())
-    );
-}
-
 fn main() {
     println!("https://adventofcode.com/2019");
 
-    let days: Vec<fn() -> String> = vec![
-        day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
-        day14, day15, day16, day17, day18,
+    let days: Vec<fn(&mut dyn BufRead) -> String> = vec![
+        days::day01,
+        days::day02,
+        days::day03,
+        days::day04,
+        days::day05,
+        days::day06,
+        days::day07,
+        days::day08,
+        days::day09,
+        days::day10,
+        days::day11,
+        days::day12,
+        days::day13,
+        days::day14,
+        days::day15,
+        days::day16,
+        days::day17,
+        days::day18,
+        // days::day19,
+        // days::day20,
+        // days::day21,
+        // days::day22,
+        // days::day23,
+        // days::day24,
     ];
 
-    let args: Vec<String> = env::args().skip(1).collect();
-
-    // No argument -> execute all day problems.
-    if args.is_empty() {
-        let now = Instant::now();
-        for i in 1..=days.len() {
-            do_day(&days, i)
-        }
-        println!(
-            "Time to execute all days: {}",
-            format_micros(now.elapsed().as_micros())
-        );
-    } else {
-        for arg in args {
-            match arg.parse::<usize>() {
-                Ok(day) if day >= 1 && day <= days.len() => do_day(&days, day),
-                Ok(day) => println!("Unknown day: {}", day),
-                Err(error) => println!("Unable to parse day number: \"{}\", error: {}", arg, error),
-            }
-        }
-    }
+    advent_of_code_common::run(days);
 }