From 933afbfa89c29d9a2373c8db7367c47237ab7ad4 Mon Sep 17 00:00:00 2001
From: Greg Burri <greg.burri@gmail.com>
Date: Tue, 10 Dec 2024 23:16:17 +0100
Subject: [PATCH] - Use of advent_of_code_common crate - Replace HashMap and
 HashSet by FxHashMap and FxHashSet (better performances)

---
 .gitmodules           |   3 +
 Cargo.toml            |  13 ++-
 advent_of_code_common |   1 +
 src/common.rs         |  25 ++--
 src/day03.rs          |  15 ++-
 src/day06.rs          |   8 +-
 src/day10.rs          |   6 +-
 src/day11.rs          |  10 +-
 src/day12.rs          |   8 +-
 src/day14.rs          |  14 +--
 src/day15.rs          |  13 +--
 src/day17.rs          |   6 +-
 src/day18.rs          |  13 ++-
 src/days.rs           | 191 ++++++++++++++++++++++++++++++
 src/main.rs           | 265 +++++-------------------------------------
 15 files changed, 299 insertions(+), 292 deletions(-)
 create mode 100644 .gitmodules
 create mode 160000 advent_of_code_common
 create mode 100644 src/days.rs

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4413612
--- /dev/null
+++ b/.gitmodules
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 410796d..db0f76e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
index 0000000..b9fa090
--- /dev/null
+++ b/advent_of_code_common
@@ -0,0 +1 @@
+Subproject commit b9fa0908044042af2ca3dd66281b58afd289b4e4
diff --git a/src/common.rs b/src/common.rs
index ab6774b..c706ad7 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -1,13 +1,18 @@
-use std::{fs, path::Path, str::FromStr};
+use std::{
+    io::{read_to_string, BufRead},
+    str::FromStr,
+};
 
-pub fn read_list_of_numbers<P, T>(file: P, sep: &str) -> Vec<T>
+pub fn read_list_of_numbers<T>(reader: &mut dyn BufRead, sep: &str) -> Vec<T>
 where
-    P: AsRef<Path>,
     T: FromStr,
-    T::Err: std::fmt::Debug
-
+    T::Err: std::fmt::Debug,
 {
-    fs::read_to_string(file).unwrap().split(sep).map(|line| line.trim().parse::<T>().unwrap()).collect()
+    read_to_string(reader)
+        .unwrap()
+        .split(sep)
+        .map(|line| line.trim().parse::<T>().unwrap())
+        .collect()
 }
 
 pub fn layer_to_printable_string(layer: &[u8], width: usize) -> String {
@@ -15,15 +20,17 @@ pub fn layer_to_printable_string(layer: &[u8], width: usize) -> String {
     let mut i = 0;
 
     loop {
-        for _ in 0 .. width {
+        for _ in 0..width {
             if layer[i] == 0 {
                 result += " ";
             } else {
                 result += "█";
             }
             i += 1;
-            if i >= layer.len() { return result }
+            if i >= layer.len() {
+                return result;
+            }
         }
         result += "\n";
     }
-}
\ No newline at end of file
+}
diff --git a/src/day03.rs b/src/day03.rs
index f6f16b8..a2cb697 100644
--- a/src/day03.rs
+++ b/src/day03.rs
@@ -1,7 +1,6 @@
-use std::{
-    collections::{HashMap, HashSet},
-    iter::{FromIterator, Iterator},
-};
+use std::iter::{FromIterator, Iterator};
+
+use rustc_hash::{FxHashMap, FxHashSet};
 
 pub fn split_movements(movements: &str) -> Vec<&str> {
     movements.split(',').collect()
@@ -43,15 +42,15 @@ fn positions(wire: &[&str]) -> Vec<(i32, i32)> {
 }
 
 pub fn manhattan_distance_from_cross_to_port(wire1: &[&str], wire2: &[&str]) -> i32 {
-    let positions_wire1: HashSet<(i32, i32)> = HashSet::from_iter(positions(wire1));
-    let positions_wire2: HashSet<(i32, i32)> = HashSet::from_iter(positions(wire2));
-    let cross: HashSet<_> = positions_wire1.intersection(&positions_wire2).collect();
+    let positions_wire1: FxHashSet<(i32, i32)> = FxHashSet::from_iter(positions(wire1));
+    let positions_wire2: FxHashSet<(i32, i32)> = FxHashSet::from_iter(positions(wire2));
+    let cross: FxHashSet<_> = positions_wire1.intersection(&positions_wire2).collect();
     cross.iter().map(|(x, y)| x.abs() + y.abs()).min().unwrap()
 }
 
 pub fn first_cross_sum_of_lengths(wire1: &[&str], wire2: &[&str]) -> usize {
     let positions_wire1 = positions(wire1);
-    let positions_wire1_indexed: HashMap<&(i32, i32), usize> = HashMap::from_iter(
+    let positions_wire1_indexed: FxHashMap<&(i32, i32), usize> = FxHashMap::from_iter(
         positions_wire1
             .iter()
             .enumerate()
diff --git a/src/day06.rs b/src/day06.rs
index 77ae402..e4a02d1 100644
--- a/src/day06.rs
+++ b/src/day06.rs
@@ -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]));
diff --git a/src/day10.rs b/src/day10.rs
index 799b0a8..3539e46 100644
--- a/src/day10.rs
+++ b/src/day10.rs
@@ -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 {
diff --git a/src/day11.rs b/src/day11.rs
index 822e218..6da63be 100644
--- a/src/day11.rs
+++ b/src/day11.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use rustc_hash::FxHashMap;
 
 use super::intcode;
 
@@ -11,7 +11,7 @@ struct Robot {
     next_command: NextCommand,
     current_pos: (i32, i32),
     current_dir: i32, // 0: up, 1: right, 2: down, 3: left.
-    panels: HashMap<(i32, i32), i64>,
+    panels: FxHashMap<(i32, i32), i64>,
 }
 
 impl Robot {
@@ -20,7 +20,7 @@ impl Robot {
             next_command: NextCommand::Paint,
             current_pos: (0, 0),
             current_dir: 0,
-            panels: HashMap::new(),
+            panels: FxHashMap::default(),
         }
     }
 }
@@ -53,7 +53,7 @@ impl intcode::IO for Robot {
     }
 }
 
-pub fn run_robot(code: &[i64], initial_value: i64) -> HashMap<(i32, i32), i64> {
+pub fn run_robot(code: &[i64], initial_value: i64) -> FxHashMap<(i32, i32), i64> {
     let mut robot = Robot::new();
     if initial_value != 0 {
         robot.panels.insert((0, 0), initial_value);
@@ -63,7 +63,7 @@ pub fn run_robot(code: &[i64], initial_value: i64) -> HashMap<(i32, i32), i64> {
     robot.panels
 }
 
-pub fn panels_to_layer(panels: &HashMap<(i32, i32), i64>) -> (Vec<u8>, usize) {
+pub fn panels_to_layer(panels: &FxHashMap<(i32, i32), i64>) -> (Vec<u8>, usize) {
     let coordinates: Vec<&(i32, i32)> = panels.keys().collect();
     let min_x = coordinates.iter().min_by_key(|(x, _)| x).unwrap().0;
     let max_x = coordinates.iter().max_by_key(|(x, _)| x).unwrap().0;
diff --git a/src/day12.rs b/src/day12.rs
index 34aefac..17bdedb 100644
--- a/src/day12.rs
+++ b/src/day12.rs
@@ -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;
     }
 }
 
diff --git a/src/day14.rs b/src/day14.rs
index e921cbd..313216a 100644
--- a/src/day14.rs
+++ b/src/day14.rs
@@ -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.
diff --git a/src/day15.rs b/src/day15.rs
index b2791e6..df45120 100644
--- a/src/day15.rs
+++ b/src/day15.rs
@@ -1,7 +1,6 @@
-use std::{
-    collections::{HashMap, HashSet},
-    iter::FromIterator,
-};
+use std::iter::FromIterator;
+
+use rustc_hash::{FxHashMap, FxHashSet};
 
 use super::intcode;
 
@@ -15,7 +14,7 @@ enum LocationState {
 
 #[derive(Clone)]
 pub struct DroidTrackingSystem {
-    board: HashMap<(i32, i32), LocationState>,
+    board: FxHashMap<(i32, i32), LocationState>,
     current_path: Vec<(i32, i32)>,
     oxygen_location: (i32, i32),
     steps_to_oxygen: i32,
@@ -25,7 +24,7 @@ pub struct DroidTrackingSystem {
 impl DroidTrackingSystem {
     fn new() -> Self {
         DroidTrackingSystem {
-            board: HashMap::from_iter(vec![((0, 0), LocationState::Visited)].into_iter()),
+            board: FxHashMap::from_iter(vec![((0, 0), LocationState::Visited)].into_iter()),
             current_path: vec![(0, 0)],
             oxygen_location: (0, 0),
             steps_to_oxygen: 0,
@@ -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 {
     let mut dts = dts.clone(); // To be mutable.
     dts.current_path = vec![dts.oxygen_location];
-    let mut visited: HashSet<(i32, i32)> = HashSet::from_iter(dts.current_path.iter().copied());
+    let mut visited: FxHashSet<(i32, i32)> = FxHashSet::from_iter(dts.current_path.iter().copied());
     let mut max_length = 0;
 
     'main: while !dts.current_path.is_empty() {
diff --git a/src/day17.rs b/src/day17.rs
index bdad2bb..08b5825 100644
--- a/src/day17.rs
+++ b/src/day17.rs
@@ -1,4 +1,6 @@
-use std::{collections::HashSet, convert::TryFrom, ops::Range};
+use std::{convert::TryFrom, ops::Range};
+
+use rustc_hash::FxHashSet;
 
 use itertools::Itertools;
 
@@ -93,7 +95,7 @@ impl RobotTrackingSystem {
     fn run_through(&mut self) {
         let (mut x, mut y) = self.start_position;
         let mut dir = self.start_dir;
-        let mut visited_locations = HashSet::<(i32, i32)>::new();
+        let mut visited_locations = FxHashSet::<(i32, i32)>::default();
         visited_locations.insert((x, y));
 
         let mut last_mov = Movement::Left;
diff --git a/src/day18.rs b/src/day18.rs
index 3bd7819..f43b105 100644
--- a/src/day18.rs
+++ b/src/day18.rs
@@ -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
index 0000000..82515b1
--- /dev/null
+++ b/src/days.rs
@@ -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, "")
+}
diff --git a/src/main.rs b/src/main.rs
index e23f773..cb3f314 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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);
 }
-- 
2.49.0