+use std::cmp::Ordering;\r
 \r
+pub fn parse_range(raw: &str) -> (i32, i32) {\r
+    let nums: Vec<i32> = raw.trim().split('-').map(|n| n.parse::<i32>().unwrap()).collect();\r
+    (nums[0], nums[1])\r
+}\r
+\r
+type Digits = Vec<u8>;\r
+\r
+fn get_digits(value: i32) -> Digits {\r
+    let mut digits = Vec::<u8>::new();\r
+    let mut value = value;\r
+    while value > 0 {\r
+        digits.push((value % 10) as u8);\r
+        value /= 10;\r
+    }\r
+    digits\r
+}\r
 \r
-fn find_password(min: i32) {\r
-    ;\r
+pub fn nb_passwords_part1(min: i32, max: i32) -> i32 {\r
+    nb_passwords(\r
+        min,\r
+        max,\r
+        &|digits: &Digits| {\r
+            for i in 1 .. digits.len() {\r
+                if digits[i - 1] == digits[i] { return true; }\r
+            }\r
+            false\r
+        }\r
+    )\r
 }\r
 \r
+pub fn nb_passwords_part2(min: i32, max: i32) -> i32 {\r
+    nb_passwords(\r
+        min,\r
+        max,\r
+        &|digits: &Digits| {\r
+            let mut last = digits[0];\r
+            let mut n = 1;\r
+            for i in 1 .. digits.len() {\r
+                if digits[i] == last {\r
+                    n += 1;\r
+                } else {\r
+                    if n == 2 { return true; }\r
+                    n = 1;\r
+                }\r
+                last = digits[i];\r
+            }\r
+            n == 2\r
+        }\r
+    )\r
+}\r
+\r
+fn nb_passwords(min: i32, max: i32, valid_password: &dyn Fn(&Digits) -> bool) -> i32 {\r
+    let mut digits = get_digits(min);\r
+    let digits_max = get_digits(max);\r
+    let l = digits.len();\r
+\r
+    fn set_range(from: usize, to: usize, value: u8, digits: &mut Digits) {\r
+        for i in from .. to { digits[i] = value; }\r
+    };\r
+\r
+    for i in (1 .. l).rev() {\r
+        if digits[i - 1] < digits[i] {\r
+            set_range(0, i, digits[i], &mut digits);\r
+            break;\r
+        }\r
+    }\r
+\r
+    let mut n = 0;\r
 \r
+    loop {\r
+        if valid_password(&digits) { n += 1; }\r
 \r
-#[cfg(test)]\r
-mod tests {\r
-    use super::*;\r
+        for i in 0 .. l {\r
+            if i == l - 1 || digits[i + 1] <= digits[i] && digits[i] != 9 {\r
+                set_range(0, i + 1, digits[i] + 1, &mut digits);\r
+                break;\r
+            }\r
+        }\r
 \r
-    #[test]\r
-    fn part1() {\r
+        for i in (0 .. l).rev() {\r
+            match digits[i].cmp(&digits_max[i]) {\r
+                Ordering::Greater => return n,\r
+                Ordering::Less => break,\r
+                Ordering::Equal => ()\r
+            }\r
+        }\r
     }\r
 }
\ No newline at end of file
 
 mod day01;
 mod day02;
 mod day03;
+mod day04;
 mod day06;
 mod day07;
 mod day08;
 mod day15;
 mod day16;
 mod day17;
+mod day18;
 
 fn day01() -> String {
     let masses = common::read_list_of_numbers("data/day01.input", "\n");
 }
 
 fn day04() -> String {
-    format!("")
+    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 {
 
 }
 
+fn day18() -> String {
+    format!("part1: {}, part2: {}", "", "")
+}
+
 fn format_micros(t: u128) -> String {
     if t < 10_000 {
         format!("{} μs", t)
         day15,
         day16,
         day17,
+        day18,
     );
 
     let args: Vec<String> = env::args().skip(1).collect();