Browse Source

Last few days I finished

niels 3 weeks ago
parent
commit
343fac3e34
60 changed files with 2359 additions and 1370 deletions
  1. 59 1
      Cargo.lock
  2. 4 3
      Cargo.toml
  3. BIN
      day17.ods
  4. 51 11
      src/bin/day1.rs
  5. 183 53
      src/bin/day10.rs
  6. 63 152
      src/bin/day11.rs
  7. 219 92
      src/bin/day12.rs
  8. 82 127
      src/bin/day13.rs
  9. 127 84
      src/bin/day14.rs
  10. 41 133
      src/bin/day15.rs
  11. 129 0
      src/bin/day16.rs
  12. 219 0
      src/bin/day17.rs
  13. 177 0
      src/bin/day18.rs
  14. 16 0
      src/bin/day19.rs
  15. 53 29
      src/bin/day2.rs
  16. 101 54
      src/bin/day3.rs
  17. 43 52
      src/bin/day4.rs
  18. 124 53
      src/bin/day5.rs
  19. 46 39
      src/bin/day6.rs
  20. 100 39
      src/bin/day7.rs
  21. 141 39
      src/bin/day8.rs
  22. 23 69
      src/bin/day9.rs
  23. 16 12
      src/common.rs
  24. 9 2
      src/exec.rs
  25. 1 1
      src/web.rs
  26. 1 1
      templates/day.rs.tpl
  27. 8 0
      test_inputs/day1-a.txt
  28. 29 0
      test_inputs/day1-b.txt
  29. 5 15
      test_inputs/day1.txt
  30. 11 0
      test_inputs/day10-a.txt
  31. 11 0
      test_inputs/day10-b.txt
  32. 10 0
      test_inputs/day10-c.txt
  33. 6 147
      test_inputs/day10.txt
  34. 11 28
      test_inputs/day11.txt
  35. 2 0
      test_inputs/day12-a.txt
  36. 7 6
      test_inputs/day12.txt
  37. 54 0
      test_inputs/day13-a.txt
  38. 15 23
      test_inputs/day13.txt
  39. 11 3
      test_inputs/day14.txt
  40. 2 16
      test_inputs/day15.txt
  41. 11 0
      test_inputs/day16.txt
  42. 14 0
      test_inputs/day17.txt
  43. 15 0
      test_inputs/day18.txt
  44. 0 0
      test_inputs/day19.txt
  45. 6 4
      test_inputs/day2.txt
  46. 10 0
      test_inputs/day3-a.txt
  47. 11 7
      test_inputs/day3.txt
  48. 7 7
      test_inputs/day4.txt
  49. 34 10
      test_inputs/day5.txt
  50. 0 2
      test_inputs/day6-1.txt
  51. 0 2
      test_inputs/day6-2.txt
  52. 0 2
      test_inputs/day6-3.txt
  53. 0 2
      test_inputs/day6-4.txt
  54. 3 2
      test_inputs/day6.txt
  55. 6 24
      test_inputs/day7.txt
  56. 6 0
      test_inputs/day8-a.txt
  57. 10 0
      test_inputs/day8-b.txt
  58. 10 6
      test_inputs/day8.txt
  59. 0 9
      test_inputs/day9-1.txt
  60. 6 9
      test_inputs/day9.txt

+ 59 - 1
Cargo.lock

@@ -30,7 +30,7 @@ dependencies = [
 ]
 
 [[package]]
-name = "aoc2022_niels_overkamp"
+name = "aoc2023_niels_overkamp"
 version = "0.1.0"
 dependencies = [
  "ansi_term",
@@ -40,6 +40,7 @@ dependencies = [
  "hex",
  "itertools",
  "nom",
+ "num",
  "petgraph",
  "primes",
  "regex",
@@ -348,6 +349,40 @@ dependencies = [
  "minimal-lexical",
 ]
 
+[[package]]
+name = "num"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.45"
@@ -358,6 +393,29 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-traits"
 version = "0.2.15"

+ 4 - 3
Cargo.toml

@@ -1,9 +1,9 @@
 [package]
-name = "aoc2022_niels_overkamp"
+name = "aoc2023_niels_overkamp"
 version = "0.1.0"
 authors = ["niels <niels.overkamp@gmail.com>"]
 edition = "2021"
-default-run = "aoc2022_niels_overkamp"
+default-run = "aoc2023_niels_overkamp"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
@@ -18,4 +18,5 @@ tempfile = "3.1.0"
 itertools = "0.10.1"
 petgraph = "0.6.0"
 primes = "0.3.0"
-nom = "7.1.1"
+nom = { version = "7.1.1", features = ["alloc"] }
+num = "0.4.0"

BIN
day17.ods


+ 51 - 11
src/bin/day1.rs

@@ -1,5 +1,5 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
-use std::result::Result::*;
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::io::Write;
 
 const DAY: &str = "day1";
 
@@ -7,19 +7,59 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
+fn to_digit(s: String) -> u32 {
+    if s.len() == 1 {
+        s.chars().next().expect("is not a digit").to_digit(10).unwrap()
+    } else {
+        match s.as_str() {
+            "one" | "eno" => 1,
+            "two" | "owt" => 2,
+            "three" | "eerht" => 3,
+            "four" | "ruof" => 4,
+            "five" | "evif" => 5,
+            "six" | "xis" => 6,
+            "seven" | "neves" => 7,
+            "eight" | "thgie" => 8,
+            "nine" | "enin" => 9,
+            s => panic!("{} is not a digit", s)
+        }
+    }
+}
+
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut calories = vec![0];
-    for calorie in input.iter() {
-        if let Ok(calorie) = calorie.parse::<u64>() {
-            *calories.last_mut().unwrap() += calorie;
+    let mut sum1: u32 = 0;
+    for line in input {
+        let mut lower = None;
+        let mut upper = None;
+        for c in line.chars() {
+            if let Some(d) = c.to_digit(10) {
+                upper.get_or_insert(d);
+                lower = Some(d);
+            }
+        }
+        if let (Some(upper), Some(lower)) = (upper, lower) {
+            sum1 += upper * 10 + lower;
+        }
+    }
+    
+    let mut sum2: u32 = 0;
+    let re_reg = regex::Regex::new(r"(one|two|three|four|five|six|seven|eight|nine|\d)").unwrap();
+    let re_rev = regex::Regex::new(r"(eno|owt|eerht|ruof|evif|xis|neves|thgie|enin|\d)").unwrap();    
+    for line in input {        
+        if let Some(capture) = re_reg.captures(line) {
+            let upper = to_digit(capture.get(1).map(|m| m.as_str().to_owned()).ok_or::<String>(format!("{} did not capture the first digit", &line).into())?);
+            if let Some(capture) = re_rev.captures(&line.chars().rev().collect::<String>()) {
+                let lower = to_digit(capture.get(1).map(|m| m.as_str().to_owned()).ok_or::<String>(format!("{} did not capture the second digit", &line).into())?);
+                sum2 += upper * 10 + lower;
+            } else {
+                return Err(format!("{} did not capture the second digit", line).into());
+            }
         } else {
-            calories.push(0);
+            return Err(format!("{} did not capture the first digit", line).into());
         }
     }
-
-    calories.sort_by(|a,b| a.cmp(b).reverse());
-
-    Ok([Some(calories[0].to_string()), Some(calories[0..3].iter().sum::<u64>().to_string())])
+    
+    Ok([Some(sum1.to_string()), Some(sum2.to_string())])
 }
 
 #[test]

+ 183 - 53
src/bin/day10.rs

@@ -1,6 +1,5 @@
-use std::{str::FromStr, num::ParseIntError};
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::HashSet;
 
 const DAY: &str = "day10";
 
@@ -8,76 +7,207 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-enum Instr {
-    NoOp, AddX(i64)
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum Curvature {
+    Right = -1, Straight = 0, Left = 1
+}
+
+impl Curvature {
+    fn as_number(&self) -> isize {
+        *self as isize
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum Direction {
+    North, East, South, West
 }
 
-impl Instr {
-    fn run(&self, reg: u64) -> u64 {
+impl Direction {
+    fn take_pipe(&self, pipe: &u8) -> Option<(Self, Curvature)> {
+        match (pipe, self) {
+            (b'|', Self::North) => Some((Self::North, Curvature::Straight)),
+            (b'|', Self::South) => Some((Self::South, Curvature::Straight)),
+            (b'-', Self::West) => Some((Self::West, Curvature::Straight)),
+            (b'-', Self::East) => Some((Self::East, Curvature::Straight)),
+            (b'L', Self::South) => Some((Self::East, Curvature::Left)),
+            (b'L', Self::West) => Some((Self::North,  Curvature::Right)),
+            (b'J', Self::South) => Some((Self::West,  Curvature::Right)),
+            (b'J', Self::East) => Some((Self::North, Curvature::Left)),
+            (b'7', Self::East) => Some((Self::South,  Curvature::Right)),
+            (b'7', Self::North) => Some((Self::West, Curvature::Left)),
+            (b'F', Self::West) => Some((Self::South, Curvature::Left)),
+            (b'F', Self::North) => Some((Self::East,  Curvature::Right)),
+            _ => None
+        }
+    }
+    
+    fn curvature(&self, other: &Direction) -> Curvature {
+        if *self == *other {
+            Curvature::Straight
+        } else if self.left() == *other {
+            Curvature::Left
+        } else {
+            Curvature::Right
+        }
+    }
+    
+    fn do_move(&self, (x, y): (usize, usize)) -> Option<(usize, usize)> {
         match self {
-            Instr::NoOp => reg,
-            Instr::AddX(v) => (reg as i64 + v) as u64,
+            Self::North => y.checked_sub(1).map(|y| (x, y)),
+            Self::East => Some((x + 1, y)),
+            Self::South => Some((x, y + 1)),
+            Self::West => x.checked_sub(1).map(|x| (x, y))
         }
     }
-
-    fn len(&self) -> u64 {
+    
+    fn left(&self) -> Self {
         match self {
-            Instr::NoOp => 1,
-            Instr::AddX(_) => 2,
+            Self::North => Self::West,
+            Self::East => Self::North,
+            Self::South => Self::East,
+            Self::West => Self::South
         }
     }
-}
-
-impl FromStr for Instr {
-    type Err = ParseIntError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        if s.starts_with("noop") {
-            Ok(Self::NoOp)
-        } else if let Some(s) = s.strip_prefix("addx ") {
-            s.parse::<i64>().map(|v| Self::AddX(v))
-        } else {
-            panic!("Niels did not bother to implement error handling correctly")
+    
+    fn right(&self) -> Self {
+        match self {
+            Self::West => Self::North,
+            Self::North => Self::East,
+            Self::East => Self::South,
+            Self::South => Self::West
         }
     }
 }
 
+const DIRECTIONS: [Direction;4] = [Direction::North, Direction::East, Direction::South, Direction::West];
 
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut pc: u64 = 0;
-    let mut reg: u64 = 1;
-    let mut screen: [bool; 40 * 6] = [false;40 * 6];
-    let mut strength: u64 = 0;
-    for line in input {
-        let instr = line.parse::<Instr>()?;
-        let new_pc = pc + instr.len();
-        let signal_match = (new_pc + 20) / 40;
-        if (pc + 20) / 40 < signal_match {
-            // println!("l: {}, pc: {}->{}, sm: {}, reg: {}, s: {}", line, pc, new_pc, signal_match, reg, strength);
-            strength += reg * (signal_match * 40  - 20);
+    let mut start = None;
+    'outer: for (y, line) in input.iter().enumerate() {
+        for (x, b) in line.as_bytes().iter().enumerate() {
+            if *b == b'S' {
+                start = Some((x,y));
+                break 'outer;
+            }
+        }
+    }
+    
+    let input: Vec<&[u8]> = input.into_iter().map(|s| s.as_bytes()).collect();
+
+    let (mut x, mut y) = start.ok_or::<Box<dyn std::error::Error>>("No S found in input".into())?;
+    let mut start_direction = None;
+    
+    if let Some(_) = Direction::East.take_pipe(&input[y][x + 1]) {
+        start_direction = Some(Direction::East);
+    } else if let Some(_) = Direction::South.take_pipe(&input[y + 1][x]) {
+        start_direction = Some(Direction::South);
+    } else if x > 0 {
+        if let Some(_) = Direction::West.take_pipe(&input[y][x - 1]) {
+            start_direction = Some(Direction::West);
+        }
+    } else if y > 0 {
+        if let Some(_) = Direction::North.take_pipe(&input[y][x - 1]) {
+            start_direction = Some(Direction::North);
         }
-        for ptr in pc..new_pc {
-            let ptr = ptr % 240;
-            let x = ptr % 40;
-            if x >= (reg.saturating_sub(1)) && x <= (reg + 1) {
-                screen[ptr as usize] = true;
+    }
+    
+    let mut direction = start_direction.ok_or::<Box<dyn std::error::Error>>("No valid connection to S found".into())?;
+    let start_direction = direction;
+    let mut loop_size = 0;
+    let mut curvature = 0;
+    
+    let mut left_outline = HashSet::new();
+    let mut line = HashSet::new();
+    let mut right_outline = HashSet::new();
+    
+    
+    loop {
+        loop_size += 1;
+        (x, y) = direction.do_move((x, y)).ok_or_else(|| -> Box<dyn std::error::Error> { format!("Invalid move at {}, {} {:?}", x, y, direction).into() })?;
+        line.insert((x,y));
+        let pipe = input[y][x];
+        let (out_direction, pipe_curvature) = if pipe == b'S' {
+            (start_direction, direction.curvature(&start_direction))
+        } else {
+            direction
+                .take_pipe(&pipe)
+                .ok_or_else(|| -> Box<dyn std::error::Error> { format!("No valid connection at {}, {} {:?}", x, y, direction).into() })?
+        };
+
+//         println!("{}, {}, {:?}-{:?}->{:?}", x, y, direction, pipe_curvature, out_direction);
+        match pipe_curvature {
+            Curvature::Left => {
+                if let Some(coord) = out_direction.right().do_move((x,y)) {
+//                     println!("{:?} right", coord);
+                    right_outline.insert(coord);
+                }
+                if let Some(coord) = direction.right().do_move((x,y)) {
+//                     println!("{:?} right", coord);
+                    right_outline.insert(coord);
+                }
+            }
+            Curvature::Right => {
+                if let Some(coord) = out_direction.left().do_move((x,y)) {
+//                     println!("{:?} left", coord);
+                    left_outline.insert(coord);
+                }
+                if let Some(coord) = direction.left().do_move((x,y)) {
+//                     println!("{:?} left", coord);
+                    left_outline.insert(coord);
+                }
             }
+            Curvature::Straight => ()
+        }
+        
+        direction = out_direction;
+        curvature += pipe_curvature.as_number();
+        
+        if pipe == b'S' {
+            break
         }
-        reg = instr.run(reg);
-        pc = new_pc;
     }
-    let mut test_str = String::with_capacity(240);
-    let mut print_str = String::with_capacity(246);
-    for (i, v) in screen.into_iter().enumerate() {
-        let c = if v {'#'} else {'.'};
-        test_str.push(c);
-        print_str.push(c);
-        if i % 40 == 39 {
-            print_str.push('\n');
+    
+//     println!("{:?}", left_outline);
+//     println!("{:?}", right_outline);
+//     println!("{:?}", line);
+//     println!("{}", curvature);
+    
+    let outline = if curvature.signum() == (Curvature::Left as isize) {
+        left_outline
+    } else {
+        right_outline
+    };
+    
+    let mut visited = HashSet::new();
+    let mut inner_count = 0;
+    
+    let mut frontier = vec![];
+    for coord in outline.into_iter() {
+        if visited.contains(&coord) || line.contains(&coord) {
+            continue
+        }
+        frontier.push(coord);
+        while let Some(coord) = frontier.pop() {
+            if visited.contains(&coord) || line.contains(&coord) {
+                continue
+            }
+            if !line.contains(&coord) {
+                println!("{:?} {:?}", coord, visited);
+                visited.insert(coord);
+                inner_count += 1;
+            }
+            DIRECTIONS.iter()
+                .for_each(|direction| {
+                    direction
+                        .do_move(coord)
+                        .filter(|coord| !visited.contains(coord) && !line.contains(coord))
+                        .map(|coord| frontier.push(coord));
+                });
         }
     }
-    println!("{}", print_str);
-    Ok([Some(strength.to_string()), Some(test_str)])
+
+    Ok([Some((loop_size / 2).to_string()), Some(inner_count.to_string())])
 }
 
 #[test]

+ 63 - 152
src/bin/day11.rs

@@ -1,6 +1,5 @@
-use std::fmt::Display;
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::HashSet;
 
 const DAY: &str = "day11";
 
@@ -8,165 +7,77 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-type Num = u64;
-type Id = usize;
-
-enum Operation {
-    Square,
-    Add(Num),
-    Mul(Num),
-}
-
-impl Operation {
-    fn apply(&self, num1: Num) -> Num {
-        match self {
-            Operation::Square => num1 * num1,
-            Operation::Add(num2) => num1 + num2,
-            Operation::Mul(num2) => num1 * num2,
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut galaxy_rows = HashSet::new();
+    let mut galaxy_columns = HashSet::new();
+    let mut galaxies = vec![];
+    
+    let (mut x, mut y) = (0usize, 0usize);    
+    for line in input.iter() {
+        x = 0;
+        for byte in line.as_bytes().iter() {
+            if *byte == b'#' {
+                galaxy_rows.insert(y);
+                galaxy_columns.insert(x);
+                galaxies.push((x,y));
+            }
+            x += 1;
         }
+        y += 1
     }
-}
-
-impl Display for Operation {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Operation::Square => write!(f,"^2"),
-            Operation::Add(num) => write!(f, "+{}", num),
-            Operation::Mul(num) => write!(f, "*{}", num),
+    
+    let mut expand_map_rows = vec![];
+    let mut expand_map_columns = vec![];
+    
+    let mut expansion = 0;
+    for x in 0..x {
+        if !galaxy_columns.contains(&x) {
+            expansion += 1;
         }
+        expand_map_columns.push(expansion)
     }
-}
-
-struct Monkey {
-    oper: Operation,
-    div_test: Num,
-    if_true: Id,
-    if_false: Id,
-}
-
-pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut items_vec: Vec<(Id, Num)> = vec![];
-    let mut monkeys: Vec<Monkey> = vec![];
-    for (i, monkey) in input.chunks(7).enumerate() {
-        if let [_, items, oper, div_test, if_true, if_false, ..] = monkey {
-            let new_items = items
-                .strip_prefix("  Starting items: ")
-                .ok_or_else(|| format!("Failed to parse monkey at items: {}\n{:?}", items, monkey))
-                .and_then(|items| {
-                    items
-                        .split(", ")
-                        .map(|item| {
-                            item.parse::<Num>()
-                                .map(|n| (i, n))
-                                .map_err(|e| e.to_string())
-                        })
-                        .collect::<Result<Vec<_>, _>>()
-                })?;
-            items_vec.extend(new_items);
-
-            monkeys.push(Monkey {
-                oper: oper
-                    .strip_prefix("  Operation: new = old ")
-                    .ok_or_else(|| {
-                        format!("Failed to parse monkey at oper: {}\n{:?}", oper, monkey)
-                    })
-                    .and_then(|oper| {
-                        if oper.starts_with("* old") {
-                            Ok(Operation::Square)
-                        } else if let Some(num) = oper.strip_prefix("* ") {
-                            num.parse::<Num>()
-                                .map(|op| Operation::Mul(op))
-                                .map_err(|e| e.to_string())
-                        } else if let Some(num) = oper.strip_prefix("+ ") {
-                            num.parse::<Num>()
-                                .map(|op| Operation::Add(op))
-                                .map_err(|e| e.to_string())
-                        } else {
-                            Err(format!(
-                                "Failed to parse monkey at oper: {}\n{:?}",
-                                oper, monkey
-                            ))
-                        }
-                    })?,
-                div_test: div_test
-                    .strip_prefix("  Test: divisible by ")
-                    .ok_or_else(|| {
-                        format!(
-                            "Failed to parse monkey at div_test: {}\n{:?}",
-                            div_test, monkey
-                        )
-                    })
-                    .and_then(|div_test| div_test.parse::<Num>().map_err(|e| e.to_string()))?,
-                if_true: if_true
-                    .strip_prefix("    If true: throw to monkey ")
-                    .ok_or_else(|| {
-                        format!(
-                            "Failed to parse monkey at if_true: {}\n{:?}",
-                            if_true, monkey
-                        )
-                    })
-                    .and_then(|if_true| if_true.parse::<Id>().map_err(|e| e.to_string()))?,
-                if_false: if_false
-                    .strip_prefix("    If false: throw to monkey ")
-                    .ok_or_else(|| {
-                        format!(
-                            "Failed to parse monkey at if_false: {}\n{:?}",
-                            if_false, monkey
-                        )
-                    })
-                    .and_then(|if_false| if_false.parse::<Id>().map_err(|e| e.to_string()))?,
-            })
-        } else {
-            return Err(format!("Failted to parse monkey {:?}", monkey).into());
+    
+    expansion = 0;
+    for y in 0..y {
+        if !galaxy_rows.contains(&y) {
+            expansion += 1;
         }
+        expand_map_rows.push(expansion)
     }
-    let mut inspection_counts: Vec<usize> = vec![0; monkeys.len()];
-    for (mut monkey_id, mut item) in items_vec.clone().into_iter() {
-        let mut i = 0;
-        while i < 20 {
-            let monkey = &monkeys[monkey_id];
-            item = monkey.oper.apply(item) / 3;
-            inspection_counts[monkey_id] += 1;
-            let new_monkey_id = if item % monkey.div_test == 0 {
-                monkey.if_true
-            } else {
-                monkey.if_false
-            };
-            if new_monkey_id < monkey_id {
-                i += 1;
-            }
-            monkey_id = new_monkey_id;
+    
+    println!("{:?}", expand_map_columns);
+    println!("{:?}", expand_map_rows);
+    
+    let mut old_galaxies = galaxies.clone();
+    
+    for (x, y) in galaxies.iter_mut() {
+        *x += expand_map_columns[*x];
+        *y += expand_map_rows[*y];
+    }
+    
+    for (x, y) in old_galaxies.iter_mut() {
+        *x += (1_000_000 - 1) * expand_map_columns[*x];
+        *y += (1_000_000 - 1) * expand_map_rows[*y];
+    }
+    
+    println!("{:?}", galaxies);
+    println!("{:?}", old_galaxies);
+    
+    let mut distance_sum = 0;
+    for (i, (x0, y0)) in galaxies.iter().enumerate() {
+        for (x1, y1) in galaxies[i+1..].iter() {
+            distance_sum += (*x1).abs_diff(*x0) + (*y1).abs_diff(*y0);
         }
     }
-    inspection_counts.sort();
-    let inspection_count1 =
-        (inspection_counts[monkeys.len() - 1] * inspection_counts[monkeys.len() - 2]).to_string();
-
-    let world = monkeys.iter().map(|m| m.div_test).reduce(|d1, d2| d1*d2).unwrap_or(1);
-
-    let mut inspection_counts: Vec<usize> = vec![0; monkeys.len()];
-    for (mut monkey_id, mut item) in items_vec.into_iter() {
-        let mut i = 0;
-        while i < 10000 {
-            let monkey = &monkeys[monkey_id];
-            item = monkey.oper.apply(item) % world;
-            inspection_counts[monkey_id] += 1;
-            let new_monkey_id = if item % monkey.div_test == 0 {
-                monkey.if_true
-            } else {
-                monkey.if_false
-            };
-            if new_monkey_id < monkey_id {
-                i += 1;
-            }
-            monkey_id = new_monkey_id;
+    
+    let mut old_distance_sum = 0;
+    for (i, (x0, y0)) in old_galaxies.iter().enumerate() {
+        for (x1, y1) in old_galaxies[i+1..].iter() {
+            old_distance_sum += (*x1).abs_diff(*x0) + (*y1).abs_diff(*y0);
         }
     }
-    inspection_counts.sort();
-    let inspection_count2 =
-        (inspection_counts[monkeys.len() - 1] * inspection_counts[monkeys.len() - 2]).to_string();
-
-    Ok([Some(inspection_count1), Some(inspection_count2)])
+    
+    Ok([Some(distance_sum.to_string()), Some(old_distance_sum.to_string())])
 }
 
 #[test]

+ 219 - 92
src/bin/day12.rs

@@ -1,9 +1,9 @@
-use std::{
-    cmp::Reverse,
-    collections::{BinaryHeap, HashMap, HashSet},
-};
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use itertools::Itertools;
+use std::iter::once;
+use std::thread;
+use std::error::Error;
+use std::collections::HashMap;
 
 const DAY: &str = "day12";
 
@@ -11,109 +11,236 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-enum Dirs {
-    Left,
-    Up,
-    Right,
-    Down,
-}
-
-impl Dirs {
-    fn apply(&self, (x, y): (usize, usize)) -> (usize, usize) {
-        match self {
-            Dirs::Left => (x.wrapping_sub(1), y),
-            Dirs::Up => (x, y + 1),
-            Dirs::Right => (x + 1, y),
-            Dirs::Down => (x, y.wrapping_sub(1)),
+fn count_valid_solutions(row: &[u8], description: &Vec<usize>) -> usize {
+    let total_groups = row.len() - description.iter().sum::<usize>() + 1;
+    let mut sum = 0;
+    for solution in (0..total_groups).combinations(description.len()) {
+        let mut valid = true;
+        let mut offset = 0;
+        let mut last = 0;
+        for (index, size) in solution.iter().zip(description.iter()) {
+            let start = index + offset;
+            let end = index+offset+size;
+            // eprint!(" {:?}{:?},", 
+//                 Vec::from_iter(row[last..start].into_iter()).into_iter().map(|n| *n as char).collect::<Vec<_>>(),
+//                 Vec::from_iter(row[start..end].into_iter()).into_iter().map(|n| *n as char).collect::<Vec<char>>());
+            valid &= row[start..end].into_iter().all(|byte| *byte != b'.');
+            valid &= row[last..start].into_iter().all(|byte| *byte != b'#');
+            if !valid {
+                break;
+            }
+            offset += size;
+            last = end;
+        }
+        valid &= row[last..].into_iter().all(|byte| *byte != b'#');
+        if valid {
+            // eprintln!(" valid");
+            sum += 1;
+        } else {
+            // eprintln!("");
         }
     }
+    return sum
 }
 
-const DIRECTIONS: [Dirs; 4] = [Dirs::Left, Dirs::Up, Dirs::Right, Dirs::Down];
+fn fast_count_valid_solutions(row: &[u8], description: &Vec<usize>) -> usize {
+    let description_length = description.len();    
+    let row_length = row.len();
+    
+    let mut stack: Vec<(usize, usize, Option<usize>)> = vec![];
+    
+    let mut index = 0;
+    let mut size = description[index];
+    let mut offset = 0;
+    
+    let mut cache = HashMap::new();
 
-pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut map: HashMap<(usize, usize), u8> = HashMap::new();
-    let mut start: Option<(usize, usize)> = None;
-    let mut goal: Option<(usize, usize)> = None;
-    for (y, line) in input.iter().enumerate() {
-        for (x, c) in line.bytes().enumerate() {
-            map.insert(
-                (x, y),
-                match c {
-                    b'S' => 0,
-                    b'E' => 25,
-                    c => c - b'a',
-                },
-            );
-            match c {
-                b'S' => start = Some((x, y)),
-                b'E' => goal = Some((x, y)),
-                _ => (),
+    let count = 'outer: loop {
+        // eprintln!("i:{} o:{} s:{}",index ,offset, size);
+        // eprintln!(" {:?}", cache);
+        // eprintln!(" {:?}", stack);
+        let mut cut = false;
+        let mut valid = true;
+        let mut offset_delta = 0;
+        let mut count = 0;
+        
+        if let Some(cached_count) = cache.get(&(index, offset)) {
+            // eprint!(" cache hit");
+            cut = true;
+            count = *cached_count;
+        } else if offset + size > row_length {
+            // eprint!(" too long");
+            cut = true;
+        } else if let Some(blank) = row[offset..offset+size]
+                                .into_iter()
+                                .position(|byte| *byte == b'.') 
+        {
+            // eprint!(" blank at {}", offset+blank);
+            if row[offset..offset+blank].into_iter().any(|byte| *byte == b'#') {
+                // eprint!(" but fill before");
+                cut = true;
+            } else {
+                // eprint!(" offset+={}", blank + 1);
+                offset_delta = blank + 1;
             }
+        } else if offset + size < row_length && row[offset+size] == b'#' {
+            // eprint!(" fill at end spacer");
+            if row[offset] == b'#' {
+                // eprint!(" and fill at start");
+                cut = true;
+            } else {
+                // eprint!(" offset++");
+                offset_delta = 1;
+            }
+        } else if index == description_length - 1 {
+            if let Some(filled) = row[offset+size..]
+                                    .into_iter()
+                                    .position(|byte| *byte == b'#') 
+            {
+                // eprint!(" fill after last group at {}", filled + size + offset);
+                if row[offset..offset+filled+1]
+                    .into_iter()
+                    .any(|byte| *byte == b'#')
+                {
+                    // eprint!(" and fill before with too large gap");
+                    cut = true;
+                } else {
+                    // eprint!(" offset+={}", filled + 1);
+                    offset_delta = filled + 1;
+                }
+            } else {
+                // eprint!(" completed");
+                if row[offset] == b'#' {
+                    // eprint!(" VALID\n\tbut terminates after");
+                    cut = true;
+                } else {
+                    // eprint!(" offset++ VALID");
+                    offset_delta = 1;
+                }
+                count = 1;
+            }
+        } else {
+            stack.push((index, offset, None));
+            
+            offset_delta = size + 1;
+            offset += offset_delta;        
+            index += 1;
+            size = description[index];
+            // eprintln!(" STEP");
+            continue;
         }
-    }
-    let start = start.ok_or("No start found")?;
-    let goal = goal.ok_or("No end found")?;
-
-    // A* Search
-    let h = |(x, y)| goal.0.abs_diff(x) + goal.1.abs_diff(y);
-
-    let mut frontier = BinaryHeap::from([Reverse((h(start), start))]);
-    let mut visited = HashSet::from([start]);
-    let mut g_scores = HashMap::from([(start, 0usize)]);
-    let mut f_scores = HashMap::from([(start, h(start))]);
-    let mut length = None;
-
-    while let Some(Reverse((_, pos))) = frontier.pop() {
-        if pos == goal {
-            length = g_scores.get(&goal);
-            break;
-        }
-        let height = *map.get(&pos).unwrap();
-        let g_score = *g_scores.get(&pos).unwrap();
-        for dir in DIRECTIONS {
-            let neighbour = dir.apply(pos);
-            if let Some(_) = map.get(&neighbour).filter(|hn| **hn <= height + 1) {
-                if *g_scores.get(&neighbour).unwrap_or(&usize::MAX) > g_score + 1 {
-                    g_scores.insert(neighbour, g_score + 1);
-                    let f_score = g_score + 1 + h(neighbour);
-                    f_scores.insert(neighbour, f_score);
-                    if !visited.contains(&neighbour) {
-                        frontier.push(Reverse((f_score, neighbour)));
-                        visited.insert(neighbour);
+        
+        if cut {
+            // eprintln!(" CUT");
+            // eprintln!("{:?}\n{:?}", cache, stack);
+            cache.insert((index, offset), count);
+            'inner: loop {
+                // eprintln!("{:?}\n{:?}", cache, stack);
+                match stack.pop() {
+                    Some((entry_index, entry_offset, Some(entry_count)))    
+                    => {
+                        count += entry_count;
+                        cache.insert((entry_index, entry_offset), count);
+                    }
+                    Some((entry_index, entry_offset, None))                 
+                    => {
+                        if row[entry_offset] != b'#' {
+                            stack.push((entry_index, entry_offset, Some(count)));
+                            index = entry_index;
+                            offset = entry_offset + 1;
+                            size = description[index];  
+                            break 'inner;
+                        } else {
+                            cache.insert((entry_index, entry_offset), count);
+                        }
+                    }
+                    None 
+                    => {
+                        // eprintln!("DONE");
+                        break 'outer count;
                     }
                 }
             }
+            // eprintln!("{:?}\n{:?}", cache, stack);
+            continue;
+        } else {
+            stack.push((index, offset, Some(count)));
+            offset += offset_delta;
+            // eprintln!("");
         }
-    }
-
-    let length = length.ok_or("No path to goal found")?;
+    };
+    // println!("\t\t{}", count);
+    return count;
+}
 
-    let mut frontier = BinaryHeap::from([Reverse((0, goal))]);
-    let mut visited = HashSet::from([goal]);
-    let mut shortest_path = usize::MAX;
 
-    while let Some(Reverse((length, pos))) = frontier.pop() {
-        if *map.get(&pos).unwrap() == 0 {
-            shortest_path = shortest_path.min(length);
-        }
-        let height = *map.get(&pos).unwrap();
-        for neighbour in DIRECTIONS
-            .iter()
-            .map(|d| d.apply(pos))
-            .filter(|pos| !visited.contains(pos))
-            .filter(|pos| map.get(pos).map(|h| (*h + 1) >= height).unwrap_or(false))
-            .collect::<Vec<_>>()
-        {
-            frontier.push(Reverse((length + 1, neighbour)));
-            visited.insert(neighbour);
-        }
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut input = input.clone();
+    let mut sum = 0;
+    let mut long_sum = 0;
+    
+    let mut handles = vec![];
+    let threads = 1;
+    for i in 0..threads {
+        let input = input.split_off(input.len() - input.len() / (threads - i));
+        let handle = thread::spawn(move || -> Result<_, String> {
+            let mut sum = 0;
+            let mut long_sum = 0;
+            let l = input.len();
+            for (j, line) in input.into_iter().enumerate() {
+                let temp = sum;
+                if let Some((row, description)) = line.split_once(' ') {
+                    let description = description
+                        .split(',')
+                        .map(str::parse::<usize>)
+                        .collect::<Result<Vec<usize>, _>>()
+                        .map_err(|e| e.to_string())?;
+//                     print!("{}", row);
+                    let row = row.as_bytes();
+                        
+                    sum += fast_count_valid_solutions(row, &description);
+                    
+                    let len = description.len()*5;
+                    let description = Vec::from_iter(description.into_iter().cycle().take(len));
+                    let len = row.len()*5 + 4;
+                    let row: Vec<u8> = Vec::from_iter(row.into_iter().map(|n| *n).chain(once(b'?')).cycle().take(len));
+//                     print!("\tlong");
+                    long_sum += fast_count_valid_solutions(row.as_slice(), &description);
+                } else {
+                    return Err(format!("{} is not a valid \"row description\" format", line));
+                }
+                println!("{}: ({}/{})\t", i, j + 1, l);
+            }
+            return Ok((sum, long_sum));
+        });
+        handles.push(handle);
     }
-
-    Ok([Some(length.to_string()), Some(shortest_path.to_string())])
+    
+    for handle in handles.into_iter() {
+        let (result, long_result) = (handle.join().unwrap())?;
+        sum +=  result;
+        long_sum += long_result;
+    }
+    Ok([Some(sum.to_string()), Some(long_sum.to_string())])
 }
 
 #[test]
 pub fn test_day12() {
     assert!(common::run_test(DAY, &run))
+//     loop {
+//         let mut line = String::new();
+//         std::io::stdin().read_line(&mut line).unwrap();
+//         write!(std::io::stderr(), "{}", line);
+//         match run(&Vec::from([line[..line.len()-1].to_owned()])) {
+//             Ok([v, _]) => {
+//                 write!(std::io::stderr(), "{}\n", v.unwrap()).unwrap();
+//                 std::io::stderr().flush().unwrap();
+//             }
+//             Err(e) => {
+//                 write!(std::io::stderr(), "{}\n", e).unwrap();
+//                 std::io::stderr().flush().unwrap();
+//             }
+//         }
+//     }
 }
+

+ 82 - 127
src/bin/day13.rs

@@ -1,16 +1,5 @@
-use std::cmp::Ordering;
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
-use nom::{
-    branch::alt,
-    bytes::complete::tag,
-    character::complete::{char as char_parser, u64 as u64_parser},
-    combinator::{all_consuming, value},
-    error::Error,
-    multi::many0,
-    sequence::terminated,
-    Finish, Parser,
-};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::iter;
 
 const DAY: &str = "day13";
 
@@ -18,140 +7,106 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-enum SymbolsOrdering {
-    Equal,
-    Less,
-    Greater,
-    RaiseLeftNum(u64),
-    RaiseRightNum(u64),
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-enum Symbols {
-    LBracket,
-    RBracket,
-    Num(u64),
-}
-
-#[derive(PartialEq, Eq, Clone)]
-struct SymbolList {
-    l: Vec<Symbols>,
-}
-
-impl Symbols {
-    fn lex<'a>(input: &'a str) -> Result<SymbolList, Error<&'a str>> {
-        let l_bracket = value(Self::LBracket, char_parser('['));
-        let r_bracket = value(Self::RBracket, char_parser(']'));
-        let num = u64_parser.map(|n| Self::Num(n));
-        let symbol = alt((l_bracket, r_bracket, num));
-        let comma0 = tag(",").or(tag(""));
-        let symbol_comma0 = terminated(symbol, comma0);
-        let list = many0(symbol_comma0).map(|l| l);
-
-        all_consuming(list)(input)
-            .finish()
-            .map(|(_, l)| SymbolList { l })
-    }
-
-    fn cmp(&self, other: &Self) -> SymbolsOrdering {
-        match (self, other) {
-            (Symbols::LBracket, Symbols::LBracket) | (Symbols::RBracket, Symbols::RBracket) => {
-                SymbolsOrdering::Equal
+fn find_mirror_plane(horizontal: &Vec<u16>, vertical: &Vec<u16>) -> Option<usize> {
+    let cases = [(horizontal, 100, false), (horizontal, 100, true), (vertical, 1, false), (vertical, 1, true)];
+    for (grid, mult, invert) in cases.into_iter() {
+        let l = grid.len() - 1;
+        let mut index = 1;
+        while index < grid.len() {
+            let mut mirrors = true;
+            for offset in 0..=(index/2) {
+                if (invert && grid[l-offset] != grid[l-index+offset]) 
+                    || (!invert && grid[offset] != grid[index-offset]) 
+                {
+                    mirrors = false;
+                    break;
+                }
             }
-            (Symbols::LBracket, Symbols::Num(n)) => SymbolsOrdering::RaiseRightNum(*n),
-            (Symbols::Num(n), Symbols::LBracket) => SymbolsOrdering::RaiseLeftNum(*n),
-            (Symbols::RBracket, _) => SymbolsOrdering::Less,
-            (_, Symbols::RBracket) => SymbolsOrdering::Greater,
-            (Symbols::Num(nl), Symbols::Num(nr)) => {
-                if nl < nr {
-                    SymbolsOrdering::Less
-                } else if nl > nr {
-                    SymbolsOrdering::Greater
+            if mirrors {
+                if invert {
+                    return Some(mult * (l - (index / 2)));
                 } else {
-                    SymbolsOrdering::Equal
+                    return Some(mult * ((index / 2) + 1));
                 }
             }
+            index += 2;
         }
     }
+    return None;
 }
 
-impl PartialOrd for SymbolList {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        let mut left = self.l.iter();
-        let mut right = other.l.iter();
-
-        let mut front_log_left: Vec<Symbols> = vec![];
-        let mut front_log_right: Vec<Symbols> = vec![];
-
-        while let Some((l, r)) = {
-            let l = front_log_left.pop().or_else(|| left.next().map(|s| *s));
-            let r = front_log_right.pop().or_else(|| right.next().map(|s| *s));
-            l.zip(r)
-        } {
-            match l.cmp(&r) {
-                SymbolsOrdering::Equal => (),
-                SymbolsOrdering::Less => {
-                    return Some(Ordering::Less);
-                }
-                SymbolsOrdering::Greater => {
-                    return Some(Ordering::Greater);
-                }
-                SymbolsOrdering::RaiseLeftNum(n) => {
-                    front_log_left.extend([Symbols::RBracket, Symbols::Num(n)]);
+fn find_near_mirror_plane(horizontal: &Vec<u16>, vertical: &Vec<u16>) -> Option<usize> {
+    let cases = [(horizontal, 100, false), (horizontal, 100, true), (vertical, 1, false), (vertical, 1, true)];
+    for (grid, mult, invert) in cases.into_iter() {
+        let l = grid.len() - 1;
+        let mut index = 1;
+        while index < grid.len() {
+            let mut mirrors = true;
+            let mut smudged = false;
+            for offset in 0..=(index/2) {
+                let abs_diff = if invert {
+                    grid[l-offset] ^ grid[l-index+offset]
+                } else {
+                    grid[offset] ^ grid[index-offset]
+                };
+                if abs_diff != 0 {
+                    if (abs_diff & (abs_diff - 1)) == 0 {
+                        if smudged {
+                            mirrors = false;
+                            break;
+                        } else {
+                            smudged = true;
+                        }
+                    } else {
+                        mirrors = false;
+                        break;
+                    }
                 }
-                SymbolsOrdering::RaiseRightNum(n) => {
-                    front_log_right.extend([Symbols::RBracket, Symbols::Num(n)]);
+            }
+            if mirrors && smudged {
+                if invert {
+                    println!("{}", l - (index/2));
+                    return Some(mult * (l - (index / 2)));
+                } else {
+                    println!("{}", (index/2) + 1);
+                    return Some(mult * ((index / 2) + 1));
                 }
-            };
-        }
-        if front_log_left.len() == 0
-            && front_log_right.len() == 0
-            && left.next().is_none()
-            && right.next().is_none()
-        {
-            return Some(Ordering::Equal);
-        } else {
-            return None;
+            }
+            index += 2;
         }
     }
+    return None;
 }
 
-pub fn run<'a>(input: &'a Vec<String>) -> AOCResult {
-    let mut count = 0;
-    let marker1 = SymbolList { l: vec![Symbols::LBracket, Symbols::LBracket, Symbols::Num(2), Symbols::RBracket, Symbols::RBracket] };
-    let marker2 = SymbolList { l: vec![Symbols::LBracket, Symbols::LBracket, Symbols::Num(6), Symbols::RBracket, Symbols::RBracket] };
-    let mut packets: Vec<SymbolList> = vec![marker1.clone(), marker2.clone()];
-    for (i, pair) in input.chunks(3).enumerate() {
-        if let [left, right, ..] = pair {
-            let left = Symbols::lex(left).map_err(|e| e.to_string())?;
-            let right = Symbols::lex(right).map_err(|e| e.to_string())?;
-
-            match left.partial_cmp(&right) {
-                Some(Ordering::Less) => {
-                    count += i + 1;
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut grid_horizontal = vec![];
+    let mut grid_vertical = vec![];
+    let mut summary = 0;
+    let mut smudged_summary = 0;
+
+    for line in input.into_iter().chain(iter::once(&String::new())) {
+        if line == "" {
+            summary += find_mirror_plane(&grid_horizontal, &grid_vertical).ok_or("No mirror plane found".to_string())?;
+            smudged_summary += find_near_mirror_plane(&grid_horizontal, &grid_vertical).ok_or("No smudged mirror plane found".to_string())?;            
+            grid_horizontal = vec![];
+            grid_vertical = vec![];
+        } else {
+            let mut horizontal_number = 0u16;
+            grid_vertical.resize(line.len(), 0u16);
+            for (byte, vertical_number) in line.as_bytes().into_iter().zip(grid_vertical.iter_mut()) {
+                horizontal_number <<= 1;
+                *vertical_number <<= 1;
+                if *byte == b'#' {
+                    horizontal_number |= 1;
+                    *vertical_number |= 1;
                 }
-                None => panic!("Found indentical inputs: {:?}", pair),
-                _ => (),
             }
-
-            packets.push(left);
-            packets.push(right);
+            grid_horizontal.push(horizontal_number);
         }
     }
 
-    packets.sort_by(|a, b| a.partial_cmp(b).expect("invalid comparison"));
-
-    let key = packets.into_iter()
-           .enumerate()
-           .fold(1, |b, (i, p)| {
-               if p == marker1 || p == marker2 {
-                   b * (i + 1)
-               } else {
-                   b
-               }
-           });
 
-    Ok([Some(count.to_string()), Some(key.to_string())])
+    Ok([Some(summary.to_string()), Some(smudged_summary.to_string())])
 }
 
 #[test]

+ 127 - 84
src/bin/day14.rs

@@ -1,17 +1,8 @@
-use std::collections::HashSet;
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::mem;
+use std::collections::HashMap;
+use std::collections::hash_map::Entry;
 
-use aoc2022_niels_overkamp::common::{self, AOCResult};
-
-use itertools::Itertools;
-use nom::{
-    bytes::complete::tag,
-    character::complete::u64 as u64_parser,
-    combinator::all_consuming,
-    error::Error,
-    multi::separated_list1,
-    sequence::{terminated, tuple},
-    Finish, Parser,
-};
 
 const DAY: &str = "day14";
 
@@ -19,92 +10,144 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-pub fn run(input: &Vec<String>) -> AOCResult {
-    let point_parser = tuple((terminated(u64_parser, tag(",")), u64_parser));
-    let wall_parser = separated_list1(tag(" -> "), point_parser);
-    let mut wall_parser = all_consuming(wall_parser);
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+enum Direction {
+    North, East, South, West
+}
 
-    let mut map: HashSet<(u64, u64)> = HashSet::new();
+const SPIN_CYCLE: [Direction; 4] = [Direction::North, Direction::West, Direction::South, Direction::East];
 
-    for line in input {
-        let result: Result<Vec<(u64, u64)>, Error<&str>> =
-            wall_parser.parse(line.as_str()).finish().map(|(_, l)| l);
-        let wall = result.map_err(|e| e.to_string())?;
-        for line in wall.windows(2) {
-            let (p1, p2) = if let &[p1, p2] = line {
-                (p1, p2)
-            } else {
-                panic!("windows returned not 2 length");
+fn tilt(grid: &mut Vec<Vec<u8>>, direction: Direction) -> usize {
+    let last_x = grid[0].len() - 1;
+    let last_y = grid.len() - 1;
+    
+    let mut load = 0;
+    let (last_width, height_iter) = match direction {
+        Direction::North => (last_x, (0, last_y, 1)),
+        Direction::East => (last_y, (last_x, 0, -1)),
+        Direction::South => (last_x, (last_y, 0, -1)),
+        Direction::West => (last_y, (0, last_x, 1)),
+    };
+    let platform_height = grid.len();
+    
+
+    for width in 0..=last_width {
+//         print!("*{}* ", width);
+        let mut height = height_iter.0;
+        let mut resting_point = height;
+        while height_iter.2 < 0 || height <= height_iter.1 {
+            let value = match direction {
+                Direction::North | Direction::South => grid[height][width],
+                Direction::East | Direction::West => grid[width][height],
             };
-            let (hori, iter) = if p1.0 == p2.0 {
-                (false, p1.1.min(p2.1)..=p1.1.max(p2.1))
-            } else if p1.1 == p2.1 {
-                (true, p1.0.min(p2.0)..=p1.0.max(p2.0))
+//             print!("{} {} ({}), ", resting_point, height, value as char);
+
+            match value {
+                b'O' => {
+                    if resting_point != height {
+//                         print!("[swap {} {}] ", resting_point, height);
+                        match direction {
+                            Direction::North | Direction::South => { 
+                                let (min, max) = (resting_point.min(height), resting_point.max(height));
+                                let (left, right) = grid.split_at_mut(max);
+                                mem::swap(&mut left[min][width], &mut right[0][width]);
+                            }
+                            Direction::East | Direction::West => grid[width].swap(resting_point, height),
+                        };
+                    }
+                    load += match direction {
+                        Direction::North | Direction::South => platform_height - resting_point,
+                        Direction::East | Direction::West => platform_height - width,
+                    };
+                    if height_iter.2 > 0 {
+                        resting_point += 1;
+                    } else {
+                        resting_point = resting_point.saturating_sub(1);
+                    }
+                }
+                b'#' => {
+                    if height_iter.2 > 0 {
+                        resting_point = height + 1;
+                    } else {
+                        resting_point = height.saturating_sub(1);
+                    }
+                }
+                _ => (),
+            }
+            if height_iter.2 > 0isize {
+                height += 1;
+            } else if height == 0 {
+                break
             } else {
-                return Err(format!("Found non straight line: {:?} -> {:?}", p1, p2).into());
-            };
-            for v in iter.into_iter() {
-                let p = if hori { (v, p1.1) } else { (p1.0, v) };
-                map.insert(p);
+                height -= 1;
             }
         }
+//         println!("");
     }
+    return load;
+}
 
-    let wall_map = map.clone();
-
-    let death_plane = *map.iter().map(|(_, y)| y).max().unwrap();
-
-    let floor = death_plane + 2;
-
-    let (mut x, mut y) = (500, 0);
-    let mut count = 0;
-    let mut count1 = None;
-    let count2;
-    loop {
-        if y > death_plane && count1.is_none() {
-            count1 = Some(count);
-        }
-        match (
-            y + 1 == floor,
-            map.contains(&(x, y + 1)),
-            map.contains(&(x - 1, y + 1)),
-            map.contains(&(x + 1, y + 1)),
-        ) {
-            (false, false, _, _) => (x, y) = (x, y + 1),
-            (false, _, false, _) => (x, y) = (x - 1, y + 1),
-            (false, _, _, false) => (x, y) = (x + 1, y + 1),
-            _ => {
-                map.insert((x, y));
-                count += 1;
-                if (x, y) == (500, 0) {
-                    count2 = count;
-                    break;
-                }
-                (x, y) = (500, 0);
+fn hash_key(grid: &Vec<Vec<u8>>) -> Vec<u128> {
+    let mut rows = vec![];
+    for row in grid.into_iter() {
+        let mut number = 0;
+        for byte in row.into_iter() {
+            number <<=1;
+            if *byte == b'O' {
+                number |= 1;
             }
         }
+        rows.push(number);
     }
+    return rows;
+}
 
-    let (min_x, max_x) = map.iter().map(|(x, _)| x).minmax().into_option().unwrap();
-    let (min_x, max_x) = (*min_x, *max_x);
+fn print(grid: &Vec<Vec<u8>>) {
+    let mut s = String::new();
+    for line in grid.into_iter() {
+        s += String::from_utf8(line.clone()).unwrap().as_str();
+        s.push('\n');
+    }
+    println!("{}", s);
+}
 
-    for y in 0..=death_plane {
-        let s: String = (min_x..=max_x)
-            .into_iter()
-            .map(|x| {
-                if wall_map.contains(&(x, y)) {
-                    '#'
-                } else if map.contains(&(x, y)) {
-                    '.'
-                } else {
-                    ' '
-                }
-            })
-            .collect();
-        println!("{}", s);
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut grid: Vec<Vec<u8>> = input.into_iter().map(|string| Vec::from(string.as_bytes())).collect();
+    print(&grid);
+    
+    let mut visited = HashMap::new();
+    let mut load_vec = vec![];
+    
+    let first_load = tilt(&mut grid, Direction::North);
+    visited.insert((Direction::North, hash_key(&grid)), 0);
+    load_vec.push(first_load);
+    print(&grid);
+    
+    let mut load = 0;
+    
+    let n = 4000000000;
+    
+    
+    for (i, direction) in (1..n).into_iter().zip(SPIN_CYCLE.iter().cycle().skip(1)) {
+//         println!("{} {:?}", i, direction);
+        load = tilt(&mut grid, *direction);
+//         print(&grid);
+        load_vec.push(load);
+        let key = (*direction, hash_key(&grid));
+        match visited.entry(key) {
+            Entry::Vacant(v) => { v.insert(i); }
+            Entry::Occupied(o) => { 
+                let cycle_i = o.get();
+                let cycle_size = i - cycle_i;
+                load = load_vec[cycle_i + ((n - i - 1) % cycle_size)];
+                println!("Found loop at {}-{} ({}): {}", cycle_i, i, cycle_size, load);
+//                 println!("{:?}", load_vec);
+                break;
+            }
+        }
     }
 
-    Ok([Some(count1.unwrap().to_string()), Some(count2.to_string())])
+    Ok([Some(first_load.to_string()), Some(load.to_string())])
 }
 
 #[test]

+ 41 - 133
src/bin/day15.rs

@@ -1,16 +1,5 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
-use nom::{
-    bytes::complete::tag,
-    character::{complete::i128 as i128_parser, streaming::char},
-    combinator::all_consuming,
-    error::Error,
-    sequence::{preceded, separated_pair},
-    Finish, Parser,
-};
-use std::{
-    collections::{HashMap, HashSet},
-    ops::Range,
-};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::hash_map::{HashMap, Entry};
 
 const DAY: &str = "day15";
 
@@ -18,136 +7,55 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-type Num = i128;
-type Point = (Num, Num);
-type Line = (Point, Point);
-
-fn intersection(((x1, y1), (x2, y2)): Line, ((x3, y3), (x4, y4)): Line) -> Option<Point> {
-    let d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
-
-    let tn = (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4);
-    let un = (x1 - x3) * (y1 - y2) - (y1 - y3) * (x1 - x2);
-    let b = if d > 0 {
-        tn > d || un > d || tn < 0 || un < 0
-    } else {
-        tn < d || un < d || tn > 0 || un > 0
-    };
-    if b || d == 0 {
-        return None;
-    }
-    let temp = tn * (x2 - x1);
-    if temp % d != 0 {
-        return None;
+fn HASH_hash(string: &str) -> u8 {
+    let mut acc: u8 = 0;
+    for byte in string.as_bytes().into_iter() {
+        if *byte != b'\n' {
+            acc = acc.wrapping_add(*byte);
+            acc = acc.wrapping_add(acc << 4); // * 17
+        }
     }
-
-    Some((x1 + tn * (x2 - x1) / d, y1 + tn * (y2 - y1) / d))
+    return acc;
 }
 
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut line_parser = {
-        let v_parser = |c| preceded(char(c).and(char('=')), i128_parser);
-        let point_parser = || separated_pair(v_parser('x'), tag(", "), v_parser('y'));
-        let sensor_parser = preceded(tag("Sensor at "), point_parser());
-        let beacon_parser = preceded(tag("closest beacon is at "), point_parser());
-        let line_parser = separated_pair(sensor_parser, tag(": "), beacon_parser);
-        all_consuming(line_parser)
-    };
-
-    let check_height = input.last().unwrap().parse::<Num>()?;
-
-    let check_size = check_height * 2;
-    let check_area = 0..=check_size;
-    let mut l_lines = HashSet::new();
-    let mut r_lines = HashSet::new();
-    let mut oor_intersections: HashMap<Point, usize> = HashMap::new();
-    let mut sensors = vec![];
-
-    let mut check_line = HashSet::new();
-    let mut check_line_beacons = HashSet::new();
-
-    let filter_add = |i: Option<Point>, i_map: &mut HashMap<Point, usize>| {
-        i.filter(|i| check_area.contains(&i.0) && check_area.contains(&i.1))
-            .map(|i| *i_map.entry(i).or_default() += 1)
-    };
-
-    for line in &input[..input.len() - 1] {
-        let (sensor, beacon) = line_parser(line.as_str())
-            .finish()
-            .map(|(_, l)| l)
-            .map_err(|e: Error<&str>| e.to_string())?;
-
-        let range = sensor.0.abs_diff(beacon.0) + sensor.1.abs_diff(beacon.1);
-        let dist = check_height.abs_diff(sensor.1);
-
-        let range = range as Num;
-        let dist = dist as Num;
-
-        // Part 2
-
-        sensors.push((sensor, range));
-
-        let corners = [
-            (sensor.0 + range + 1, sensor.1),
-            (sensor.0, sensor.1 + range + 1),
-            (sensor.0 - range - 1, sensor.1),
-            (sensor.0, sensor.1 - range - 1),
-        ];
-
-        let r_line1 = (corners[0], corners[1]);
-        let l_line1 = (corners[1], corners[2]);
-        let r_line2 = (corners[2], corners[3]);
-        let l_line2 = (corners[3], corners[0]);
-
-        for l_line in l_lines.iter() {
-            filter_add(intersection(*l_line, r_line1), &mut oor_intersections);
-            filter_add(intersection(*l_line, r_line2), &mut oor_intersections);
-        }
-        for r_line in r_lines.iter() {
-            filter_add(intersection(*r_line, l_line1), &mut oor_intersections);
-            filter_add(intersection(*r_line, l_line2), &mut oor_intersections);
-        }
-
-        l_lines.extend(&[l_line1, l_line2]);
-        r_lines.extend(&[r_line1, r_line2]);
-
-        // Part 1
-        let radius = (range - dist).max(-1);
-        check_line.extend(
-            Range {
-                start: (sensor.0 - radius),
-                end: (sensor.0 + radius) + 1,
+    let mut sum: usize = 0;
+    
+    let mut HASHMAP_vec = vec![HashMap::<_, (usize, u8)>::new(); 255];
+    let mut time: usize = 0;
+    
+    for line in input.into_iter() {
+        for instruction in line.split(',') {
+            sum += HASH_hash(instruction) as usize; // Part 1
+            
+            if let Some((label, lens)) = instruction.split_once('=') {
+                let lens_box = &mut HASHMAP_vec[HASH_hash(label) as usize];
+                let lens_focal = lens.parse::<u8>().unwrap();
+                match lens_box.entry(label) {
+                    Entry::Occupied(mut e) => { (*e.get_mut()).1 = lens_focal; }
+                    Entry::Vacant(mut e) => {
+                        e.insert((time, lens_focal));
+                        time += 1;
+                    }
+                }
+            } else if let Some(label) = instruction.strip_suffix('-') {
+                let lens_box = &mut HASHMAP_vec[HASH_hash(label) as usize];
+                lens_box.remove(label);
             }
-            .into_iter(),
-        );
-
-        if beacon.1 == check_height {
-            check_line_beacons.insert(beacon.0);
         }
     }
-
-    let possible_locations = oor_intersections.iter().filter(|(_, n)| **n >= 4);
-    println!("l={:?}", possible_locations.clone().map(|(i, _)| i).collect::<Vec<_>>().as_slice());
-    let mut location = None;
-    for (pos, _) in possible_locations {
-        let mut in_range = false;
-        for (sensor, range) in sensors.iter() {
-            if (sensor.0.abs_diff(pos.0) + sensor.1.abs_diff(pos.1)) as Num <= *range {
-                in_range = true;
-                break;
-            }
-        }
-        if !in_range {
-            location = Some(pos);
-            break;
+    
+    let mut focusing_power = 0;
+    
+    for (i, lens_box) in HASHMAP_vec.into_iter().enumerate() {
+        let mut lens_box = lens_box.values().collect::<Vec<_>>();
+        lens_box.sort();
+        for (j, (_, focal)) in lens_box.into_iter().enumerate() {
+            focusing_power += (i + 1) * (j + 1) * (*focal as usize);
         }
     }
 
-    let location = location.ok_or("No suitable location found")?;
-    let tuning_frequency = location.0 * check_size + location.1;
-
-    let count = check_line.difference(&check_line_beacons).count();
-
-    Ok([Some(count.to_string()), Some(tuning_frequency.to_string())])
+    Ok([Some(sum.to_string()), Some(focusing_power.to_string())])
 }
 
 #[test]

+ 129 - 0
src/bin/day16.rs

@@ -0,0 +1,129 @@
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::HashSet;
+
+
+const DAY: &str = "day16";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+enum Tile {
+    Empty, ForwardMirror, BackwardMirror, HorizontalSplitter, VerticalSplitter
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+enum Direction {
+    North, East, South, West
+}
+
+impl Tile {
+    fn apply(&self, direction: &Direction) -> (Direction, Option<Direction>) {
+        match (self, direction) {
+            (Tile::Empty, _) => (*direction, None),
+            
+            (Tile::ForwardMirror, Direction::North) => (Direction::East, None),
+            (Tile::ForwardMirror, Direction::East) => (Direction::North, None),
+            (Tile::ForwardMirror, Direction::South) => (Direction::West, None),
+            (Tile::ForwardMirror, Direction::West) => (Direction::South, None),
+            
+            (Tile::BackwardMirror, Direction::North) => (Direction::West, None),
+            (Tile::BackwardMirror, Direction::West) => (Direction::North, None),
+            (Tile::BackwardMirror, Direction::South) => (Direction::East, None),
+            (Tile::BackwardMirror, Direction::East) => (Direction::South, None),
+            
+            (Tile::HorizontalSplitter, Direction::North) => (Direction::West, Some(Direction::East)),
+            (Tile::HorizontalSplitter, Direction::West) => (Direction::West, None),
+            (Tile::HorizontalSplitter, Direction::South) => (Direction::East, Some(Direction::West)),
+            (Tile::HorizontalSplitter, Direction::East) => (Direction::East, None),
+            
+            (Tile::VerticalSplitter, Direction::North) => (Direction::North, None),
+            (Tile::VerticalSplitter, Direction::West) => (Direction::North, Some(Direction::South)),
+            (Tile::VerticalSplitter, Direction::South) => (Direction::South, None),
+            (Tile::VerticalSplitter, Direction::East) => (Direction::South, Some(Direction::North)),
+        }
+    }
+    
+    fn try_from_byte(byte: u8) -> Option<Self> {
+        match byte {
+            b'.' => Some(Self::Empty),
+            b'/' => Some(Self::ForwardMirror),
+            b'\\' => Some(Self::BackwardMirror),
+            b'-' => Some(Self::HorizontalSplitter),
+            b'|' => Some(Self::VerticalSplitter),
+            _ => None,
+        }
+    }
+}
+
+impl Direction {
+    fn apply(&self, x: usize, y: usize, max_x: usize, max_y: usize) -> Option<(usize, usize)> {
+        match self {
+            Self::North => y.checked_sub(1).map(|y| (x,y)),
+            Self::West => x.checked_sub(1).map(|x| (x,y)),
+            Self::South => (y < max_y).then_some((x,y + 1)),
+            Self::East => (x < max_x).then_some((x + 1, y)),
+        }
+    }
+}
+
+fn reflection_energized(map: &Vec<Vec<Tile>>, start: (usize, usize, Direction)) -> usize {
+    let (max_x, max_y) = (map[0].len() - 1, map.len() - 1);
+    
+    let mut visited = HashSet::from([start]);
+    let mut frontier = vec![start];
+    
+    while let Some((x,y,dir)) = frontier.pop() {
+        let (dir, extra_dir) = map[y][x].apply(&dir);
+        if let Some((x,y)) = dir.apply(x,y, max_x, max_y) {
+            if visited.insert((x, y, dir)) {
+                frontier.push((x, y, dir));
+            }
+        }
+        if let Some(dir) = extra_dir {
+            if let Some((x,y)) = dir.apply(x,y, max_x, max_y) {
+                if visited.insert((x,y,dir)) {
+                    frontier.push((x, y, dir));
+                }
+            }
+        }
+    }
+    
+    let visited: HashSet<_> = HashSet::from_iter(visited.into_iter().map(|(x,y,_)| (x,y)));
+    return visited.len();
+}
+
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let map: Vec<Vec<Tile>> = input
+        .into_iter()
+        .map(|line| line
+            .as_bytes()
+            .into_iter()
+            .map(|byte| Tile::try_from_byte(*byte).unwrap())
+            .collect::<Vec<_>>())
+        .collect::<Vec<_>>();
+    
+    let solution1 = reflection_energized(&map, (0, 0, Direction::East));
+    
+    let (max_x, max_y) = (map[0].len() - 1, map.len() - 1);
+    
+    
+    let mut solution2 = solution1;
+    for x in 0..=max_x {
+        solution2 = solution2.max(reflection_energized(&map, (x, 0, Direction::South)));
+        solution2 = solution2.max(reflection_energized(&map, (x, max_y, Direction::North)));
+    }
+    for y in 0..=max_y {
+        solution2 = solution2.max(reflection_energized(&map, (0, y, Direction::East)));
+        solution2 = solution2.max(reflection_energized(&map, (max_x, y, Direction::West)));
+    }
+    
+
+    Ok([Some(solution1.to_string()), Some(solution2.to_string())])
+}
+
+#[test]
+pub fn test_day16() {
+    assert!(common::run_test(DAY, &run))
+}

+ 219 - 0
src/bin/day17.rs

@@ -0,0 +1,219 @@
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::{BTreeMap, BinaryHeap};
+use std::cmp::Reverse;
+use std::io::Write;
+
+const DAY: &str = "day17";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+#[derive(PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord)]
+enum Direction {
+    North, East, South, West, NoDirection
+}
+
+impl Direction {
+    fn delta(self: &Self, x: usize, y: usize, end_x: usize, end_y: usize) -> Option<(usize, usize)> {
+        match self {
+            Direction::North => {
+                if y > 0 {
+                    Some((x, y - 1))
+                } else {
+                    None
+                }
+            }
+            Direction::East => {
+                if x < end_x {
+                    Some((x + 1, y))
+                } else {
+                    None
+                }
+            },
+            Direction::South => {
+                if y < end_y {
+                    Some((x, y + 1))
+                } else {
+                    None
+                }
+            },
+            Direction::West => {
+                if x > 0 {
+                    Some((x - 1, y))
+                } else {
+                    None
+                }
+            },
+            Direction::NoDirection => None
+        }
+    }
+    
+    fn left(&self) -> Self {
+        match self {
+            Direction::North => Direction::West,
+            Direction::West => Direction::South,
+            Direction::South => Direction::East,
+            Direction::East => Direction::North,
+            Direction::NoDirection => Direction::East,
+        }
+    }
+    
+    fn right(&self) -> Self {
+        match self {
+            Direction::West => Direction::North,
+            Direction::South => Direction::West,
+            Direction::East => Direction::South,
+            Direction::North => Direction::East,
+            Direction::NoDirection => Direction::South,
+        }
+    }
+}
+
+fn do_move(prev_dir: Direction, new_dir: Direction, straight_length: usize) -> Option<(Direction, usize)> {
+    let mut log = std::io::stdout();
+    if prev_dir == new_dir {        
+        if straight_length >= 3 {
+            // write!(log, " l>=3 SKIP]");
+            return None
+        }
+        Some((new_dir, straight_length + 1))
+    } else {
+        Some((new_dir, 1))
+    }
+}
+
+fn do_ultra_move(prev_dir: Direction, new_dir: Direction, straight_length: usize) -> Option<(Direction, usize)> {
+    let mut log = std::io::stdout();
+    if prev_dir == new_dir {        
+        if straight_length >= 10 {
+            // write!(log, " l>=10 SKIP]");
+            return None
+        }
+        Some((new_dir, straight_length + 1))
+    } else if prev_dir != Direction::NoDirection && straight_length < 4 {
+        // write!(log, " l<4 SKIP]");
+        None
+    } else {
+        Some((new_dir, 1))
+    }
+}
+
+fn search(map: &Vec<Vec<usize>>, end_x: usize, end_y: usize, 
+            h: &dyn Fn(usize, usize) -> usize, 
+            do_move: &dyn Fn(Direction, Direction, usize) -> Option<(Direction, usize)>) 
+    -> Option<usize> 
+{
+    let mut frontier = BinaryHeap::new();
+    frontier.push(Reverse((h(0,0) + 0, 0, (0,0,Direction::NoDirection, 0), (0,0,Direction::NoDirection, 0))));
+
+    let mut scores = BTreeMap::new();
+    scores.insert((0,0,Direction::NoDirection, 0), 0);
+    
+    let mut parents = BTreeMap::new();
+    
+    let mut final_cost = None;
+    
+    let mut log = std::io::stdout();
+    
+    while let Some(Reverse((h_score, cost, (x, y, dir, l), parent))) = frontier.pop() {
+        if x == end_x && y == end_y {
+            // write!(log, "\nReached {:?} at cost {}\n", (end_x, end_y), cost);
+            final_cost = Some(cost);
+            let mut node = &(x,y,dir,l);
+            while let Some(parent) = parents.get(node) {
+                // write!(log, "{:?}<-", parent);
+                node = parent;
+            }
+            break;
+        }
+        
+        if let Some(g_score) = scores.get(&(x, y, dir, l)) {
+            if *g_score < cost {
+                continue;
+            } else if *g_score == cost && parents.contains_key(&(x, y, dir, l)) {
+                // write!(log, "\n{:?}: {}+{}", (x, y, dir, l), cost, h_score);
+                break;
+            }
+        }
+        
+        parents.insert((x, y, dir, l), parent);
+        // write!(log, "\n{:?}: {}+{}", (x, y, dir, l), cost, h_score);
+        
+        let parent = (x, y, dir, l);
+        for direction in [dir.left(), dir.right(), dir].iter() {
+            // write!(log, " [ ->{:?}", direction);
+            if let Some((direction, l)) = do_move(dir, *direction, l) {
+                if let Some((x, y)) = direction.delta(x, y, end_x, end_y) {
+                    let candidate_g_score = cost + map[y][x];
+                    // write!(log, "={:?}", (x,y));
+                    if let Some(g_score) = scores.get(&(x, y, direction, l)) {
+                        if candidate_g_score >= *g_score {
+                            // write!(log, " had lower path({}<{}) SKIP]", g_score, candidate_g_score);
+                            continue;
+                        }
+                    }
+                    scores.insert((x, y, direction, l), candidate_g_score);
+                    let f_score = candidate_g_score + h(x,y);
+                    frontier.push(Reverse((f_score, candidate_g_score, (x, y, direction, l), parent)));
+                    // write!(log, "{:?} INSERTED]", (f_score, candidate_g_score));
+                } else {
+                    // write!(log, " {:?} at bound SKIP]", (x,y));
+                }
+            }
+        }
+//         log.flush();
+    }
+    return final_cost;
+}
+
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let map = input.into_iter().map(|s| s.chars().map(|c| c.to_digit(10).unwrap() as usize).collect::<Vec<_>>()).collect::<Vec<Vec<_>>>();
+    
+    let (end_x, end_y) = (map[0].len() - 1, map.len() - 1);
+
+    assert!(end_x == end_y);
+    let midpoint = (end_x + 1)/2;
+    
+    let radial_h_scores :Vec<usize> = (0..=midpoint)
+        .into_iter()
+        .map(|i| map[i..=end_x-i].into_iter().fold(usize::MAX, |acc, row| (*row[i..=end_x-i].into_iter().min().unwrap()).min(acc)))
+        .fold((vec![], 0), |(mut vec, acc), val| {
+            vec.push(acc);
+            (vec, acc + val)
+        }).0;
+    
+    println!("{:?}", radial_h_scores);
+    
+    let h = |x, y| {
+        let x_to_edge = midpoint - (midpoint.abs_diff(x));
+        let y_to_edge = midpoint - (midpoint.abs_diff(y));
+        if x_to_edge <= y_to_edge {
+            let cost_to_edge = radial_h_scores[x_to_edge];
+            let cost_over_mid = if x <= midpoint { 2 * (midpoint - x) } else { 0 };
+            cost_to_edge + (end_y - y) + cost_over_mid
+        } else {
+            let cost_to_edge = radial_h_scores[y_to_edge];
+            let cost_over_mid = if y <= midpoint { 2 * midpoint.abs_diff(y) } else { 0 };
+            cost_to_edge + (end_x - x) + cost_over_mid
+        }
+    };
+
+    let cost = search(&map, end_x, end_y, &h, &do_move);
+    let ultra_cost = search(&map, end_x, end_y, &h, &do_ultra_move);
+    
+    if cost.is_none() {
+        return Err("No path found".into());
+    }
+    
+    if ultra_cost.is_none() {
+        return Err("No ultra path found".into());
+    }
+    
+    Ok([Some(cost.unwrap().to_string()), Some(ultra_cost.unwrap().to_string())])
+}
+
+#[test]
+pub fn test_day17() {
+    assert!(common::run_test(DAY, &run))
+}

+ 177 - 0
src/bin/day18.rs

@@ -0,0 +1,177 @@
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::HashSet;
+
+const DAY: &str = "day18";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum Curvature {
+    Right = -1, Straight = 0, Left = 1
+}
+
+impl Curvature {
+    fn as_number(&self) -> isize {
+        *self as isize
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum Direction {
+    North, East, South, West
+}
+
+const DIRECTIONS: [Direction;4] = [Direction::North, Direction::East, Direction::South, Direction::West];
+
+impl Direction {    
+    fn curvature(&self, other: &Direction) -> Curvature {
+        if *self == *other {
+            Curvature::Straight
+        } else if self.left() == *other {
+            Curvature::Left
+        } else {
+            Curvature::Right
+        }
+    }
+    
+    fn do_move(&self, (x, y): (isize, isize)) -> (isize, isize) {
+        match self {
+            Self::North => (x, y - 1),
+            Self::East => (x + 1, y),
+            Self::South => (x, y + 1),
+            Self::West => (x - 1, y),
+        }
+    }
+    
+    fn left(&self) -> Self {
+        match self {
+            Self::North => Self::West,
+            Self::East => Self::North,
+            Self::South => Self::East,
+            Self::West => Self::South
+        }
+    }
+    
+    fn right(&self) -> Self {
+        match self {
+            Self::West => Self::North,
+            Self::North => Self::East,
+            Self::East => Self::South,
+            Self::South => Self::West
+        }
+    }
+    
+    fn from_str(s: &str) -> Option<Self> {
+        match s {
+            "U" => Some(Self::North),
+            "L" => Some(Self::West),
+            "D" => Some(Self::South),
+            "R" => Some(Self::East),
+            _ => None
+        }
+    }
+}
+
+
+
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut curvature: isize = 0;
+    let mut prev_direction: Option<Direction> = None;
+    let (mut x, mut y) = (0, 0);
+    
+    let mut left_outline = HashSet::new();
+    let mut line = HashSet::new();
+    let mut right_outline = HashSet::new();
+    
+    
+    for instruction in input.into_iter() {
+        let (direction, rest) = instruction.split_once(' ').unwrap();
+        let (length, color) = rest.split_once(' ').unwrap();
+        let direction = Direction::from_str(direction).unwrap();
+        let length: isize = length.parse()?;
+        
+        let turn_curvature = prev_direction.map(|d| d.curvature(&direction)).unwrap_or(Curvature::Straight);
+        prev_direction = Some(direction);
+        
+        match turn_curvature {
+            Curvature::Left => {
+                right_outline.insert(direction.right().do_move((x,y)));
+            }
+            Curvature::Right => {
+                left_outline.insert(direction.right().do_move((x,y)));
+            }
+            _ => {}
+        }
+        
+        for _ in 0..length {
+            (x, y) = direction.do_move((x, y));
+            line.insert((x,y));
+            right_outline.insert(direction.right().do_move((x, y)));
+            left_outline.insert(direction.left().do_move((x, y)));
+        }
+        
+        curvature += turn_curvature.as_number();
+    }
+    
+    let outline = if curvature.signum() == (Curvature::Left as isize) {
+        left_outline
+    } else {
+        right_outline
+    };
+    
+    let mut visited = HashSet::new();
+    let mut inner_count = line.len();
+    
+    let mut frontier = vec![];
+    for coord in outline.into_iter() {
+        if visited.contains(&coord) || line.contains(&coord) {
+            continue
+        }
+        frontier.push(coord);
+        while let Some(coord) = frontier.pop() {
+            if visited.contains(&coord) || line.contains(&coord) {
+                continue
+            }
+            if !line.contains(&coord) {
+                visited.insert(coord);
+                inner_count += 1;
+            }
+            for direction in DIRECTIONS.iter() {
+                let coord = direction.do_move(coord);
+                if !visited.contains(&coord) && !line.contains(&coord) {
+                    frontier.push(coord);
+                }
+            }
+        }
+    }
+    
+    let mut box_stack = vec![];
+    let mut curvature: isize = 0;
+    let mut start_direction = None;
+    let mut prev_direction = None;
+    let mut box_points = [None; 4]
+    for instruction in input.into_iter() {
+        let (direction, rest) = instruction.split_once(' ').unwrap();
+        let (length, color) = rest.split_once(' ').unwrap();
+        let direction = Direction::from_str(direction).unwrap();
+        let length: isize = length.parse()?;
+        
+        let turn_curvature = prev_direction.map(|d| d.curvature(&direction)).unwrap_or(Curvature::Straight);
+        prev_direction = Some(direction);
+        start_direction = start_direction.or(prev_direction);
+        
+        curvature += turn_curvature.as_number();
+        
+        box_points[curvature.abs()]
+        
+    }
+
+    Ok([Some(inner_count.to_string()), None])
+}
+
+#[test]
+pub fn test_day18() {
+    assert!(common::run_test(DAY, &run))
+}

+ 16 - 0
src/bin/day19.rs

@@ -0,0 +1,16 @@
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day19";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+pub fn run(input: &Vec<String>) -> AOCResult {
+    Ok([None, None])
+}
+
+#[test]
+pub fn test_day19() {
+    assert!(common::run_test(DAY, &run))
+}

+ 53 - 29
src/bin/day2.rs

@@ -1,4 +1,12 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use nom::sequence::{preceded, terminated, tuple};
+use nom::bytes::complete::tag;
+use nom::character::complete::{u32 as u32_parser, alpha1};
+use nom::multi::separated_list0;
+use nom::combinator::all_consuming;
+use nom::error::{VerboseError, convert_error};
+use nom::Finish;
+use std::collections::HashMap;
 
 const DAY: &str = "day2";
 
@@ -6,35 +14,51 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-pub fn run(input: &Vec<String>) -> AOCResult {
-    let score1: u64 = input.iter().map(|line| match line.as_str() {
-            "A X" => 3 + 1,
-            "A Y" => 6 + 2,
-            "A Z" => 0 + 3,
-            "B X" => 0 + 1,
-            "B Y" => 3 + 2,
-            "B Z" => 6 + 3,
-            "C X" => 6 + 1,
-            "C Y" => 0 + 2,
-        "C Z" => 3 + 3,
-        _ => 0
-        }).sum();
+pub fn run<'a>(input: &'a Vec<String>) -> AOCResult {
+    let total = HashMap::from([("red", 12),("green", 13),("blue", 14)]);
+    
+    let id_parser = preceded(tag("Game "), terminated(u32_parser::<&'a str, VerboseError<&'a str>>, tag(": ")));
+    let cubes_parser = tuple((terminated(u32_parser, tag(" ")), alpha1));
+    let pull_parser = separated_list0(tag(", "), cubes_parser);
+    let mut game_parser = all_consuming(tuple((id_parser, separated_list0(tag("; "), pull_parser))));
+    
+    let mut count1 = 0;
+    let mut count2 = 0;
+    
+    for line in input.iter() {
+        let output = game_parser(line.as_str())
+            .finish()
+            .map_err(|e| convert_error(line.as_str(), e))?;
+            
+        let (_res, (id, game)) = output;
+            
+        let mut valid = true;
+        let mut min_amounts = HashMap::from([("red", 0), ("green", 0), ("blue", 0)]);
+        for pull in game {
+            let mut amounts = HashMap::from([("red", 0), ("green", 0), ("blue", 0)]);
+            for (amount, color) in pull {
+                if let Some(v) = amounts.get_mut(color) {
+                    *v += amount;
+                }
+                if let Some(v) = min_amounts.get_mut(color) {
+                    *v = amount.max(*v);
+                }
+            }
+            if valid {
+                for color in ["red", "green", "blue"] {
+                    if total[color] < amounts[color] {
+                        valid = false;
+                    }
+                }
+            }
+        }
+        if valid {
+            count1 += id;
+        }
+        count2 += min_amounts.into_values().product::<u32>();
+    }
 
-    let score2: u64 = input.iter().map(|line| match line.as_str() {
-            "A X" => 0 + 3,
-            "A Y" => 3 + 1,
-            "A Z" => 6 + 2,
-            "B X" => 0 + 1,
-            "B Y" => 3 + 2,
-            "B Z" => 6 + 3,
-            "C X" => 0 + 2,
-            "C Y" => 3 + 3,
-        "C Z" => 6 + 1,
-        _ => 0
-        }).sum();
-
-
-    Ok([Some(score1.to_string()), Some(score2.to_string())])
+    Ok([Some(count1.to_string()), Some(count2.to_string())])
 }
 
 #[test]

+ 101 - 54
src/bin/day3.rs

@@ -1,73 +1,120 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::{HashSet, HashMap};
 
 const DAY: &str = "day3";
 
-const OFFSET_LOWER_BYTE: u8 = b'a'; // 0x61
-const OFFSET_UPPER_BYTE: u8 = b'A'; // 0x41
-
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
+struct Code {
+    start_x: usize,
+    end_x: usize,
+    y: usize,
+    code: usize
+}
+
+
+
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut running_sum = 0;
-    let mut running_group_sum = 0;
-    let mut group_i = 0;
-    let mut group_union = !0;
-    for line in input {
-        let line = line.as_bytes();
-        let mut left = 0u64;
-        for b in &line[..line.len()/2] {
-            let prio = if *b > OFFSET_LOWER_BYTE {
-               *b - OFFSET_LOWER_BYTE
-            } else {
-                *b - OFFSET_UPPER_BYTE + 26
-            };
-            left |= 1 << (prio as u64);
+    let mut symbols = HashSet::new();
+    let mut gears = HashMap::new();
+    
+/*    let connects_le = |x, y, ss: &mut HashSet<_>, sg: &mut HashMap<_, _>| {
+        if x > 0 {
+            let symbol_connects = ss.contains(&(x - 1, y)) 
+                                || ss.contains(&(x - 1, y + 1))
+                                || (y > 0 && ss.contains(&(x - 1, y - 1)));
+            
+            let connecting_gear = sg.get_mut(&(x - 1, y)) 
+                .or(sg.get_mut(&(x - 1, y + 1)))
+                .or(sg.get_mut(&(x - 1, y - 1)));
+                
+            (symbol_connects, connecting_gear)
+        } else {
+            let symbol_connects = ss.contains(&(x, y + 1)) 
+                                || (y > 0 && ss.contains(&(x, y - 1)));
+                                
+            let connecting_gear = sg.get_mut(&(x, y + 1)) 
+                .or(sg.get_mut(&(x, y - 1)));
+            
+            (symbol_connects, connecting_gear)
         }
-        let mut right = 0u64;
-        for b in &line[line.len()/2..] {
-            let prio = if *b > OFFSET_LOWER_BYTE {
-               *b - OFFSET_LOWER_BYTE
-            } else {
-                *b - OFFSET_UPPER_BYTE + 26
-            };
-            right |= 1 << (prio as u64);
+    };
+    
+    let connects_gt = |x, y, ss: &mut HashSet<_>, sg: &mut HashMap<_, _>| {
+            let symbol_connects = ss.contains(&(x, y)) 
+                                || ss.contains(&(x, y + 1)) 
+                                || (y > 0 && ss.contains(&(x, y - 1)));
+                                
+            let connecting_gear = sg.get_mut(&(x, y))
+                .or(sg.get_mut(&(x, y + 1))) 
+                .or(sg.get_mut(&(x, y - 1)));
+            
+            (symbol_connects, connecting_gear)
+    };
+ */   
+    for (y, row) in input.iter().enumerate() {
+        for (x, c) in row.chars().enumerate() {
+            if !(c.is_digit(10) || c == '.') {
+                symbols.insert((x,y));
+            }
+            if c == '*' {
+                gears.insert((x,y), [1, 0]);
+            }
         }
-
-        let duplicate = left & right;
-        let mut check = 1;
-        let mut prio = None;
-        for i in 1..53 {
-            if duplicate == check {
-                prio = Some(i);
-                break;
+    }
+    
+    let mut codes = Vec::new();    
+    
+    for (y, row) in input.iter().enumerate() {
+        let mut code = None;
+        for (x, c) in row.chars().enumerate() {
+            if let Some(d) = c.to_digit(10) {
+                let code = code.get_or_insert(Code {start_x: x, end_x: x, y: y, code: 0});
+                code.code *= 10;
+                code.code += d as usize;
+                code.end_x = x;
+            } else if let Some(code) = code.take() {
+                codes.push(code);
             }
-            check <<= 1;
         }
-        let prio = prio.unwrap();
-        running_sum += prio;
-
-        let union = left | right;
-        group_union &= union;
-        println!("{}, {}", union, group_union);
-        let mut check = 1;
-        if group_i == 2 {
-            group_i = 0;
-            for i in 1..53 {
-                if group_union == check {
-                    running_group_sum += i;
-                    break;
+        if let Some(code) = code {
+            codes.push(code);
+        }
+    }
+    
+    let mut sum = 0;
+    for Code { start_x, end_x, y, code } in codes {
+        let mut connected = false;
+        for x in (1.max(start_x) - 1)..=(end_x+1) {
+            for y in (1.max(y) - 1)..=(y+1) {
+                if !connected && symbols.contains(&(x,y)) {
+                    connected = true;
+                    sum += code;
+                }
+                if let Some(gear) = gears.get_mut(&(x,y)) {
+                    let [ratio, count] = *gear;
+                    if count == 1 {
+                        *gear = [ratio * code, 2]
+                    } else if count == 0 {
+                        *gear = [code, 1]
+                    } else {
+                        *gear = [0, count + 1]
+                    }
                 }
-                check <<= 1;
             }
-            group_union = !0;
-        } else {
-            group_i += 1;
         }
-
     }
-    Ok([Some(running_sum.to_string()), Some(running_group_sum.to_string())])
+    
+    let mut gear_sum = 0;
+    for [ratio, count] in gears.values() {
+        if *count == 2 {
+            gear_sum += ratio;
+        }
+    }
+    
+    Ok([Some(sum.to_string()), Some(gear_sum.to_string())])
 }
 
 #[test]

+ 43 - 52
src/bin/day4.rs

@@ -1,64 +1,55 @@
-use std::{ops::Range, num::ParseIntError, str::FromStr};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use nom::character::complete::multispace1;
+use nom::sequence::{preceded, terminated, tuple};
+use nom::bytes::complete::tag;
+use nom::character::complete::u32 as u32_parser;
+use nom::error::convert_error;
+use nom::multi::separated_list0;
+use nom::combinator::all_consuming;
+use nom::Finish;
+use std::collections::HashSet;
 
-use aoc2022_niels_overkamp::common::{self, AOCResult};
 
 const DAY: &str = "day4";
 
-struct RangePair {
-    range1: Range<u64>,
-    range2: Range<u64>
-}
-
-impl RangePair {
-    fn self_contains(&self) -> bool {
-        (self.range1.start >= self.range2.start && self.range1.end <= self.range2.end) ||
-            (self.range2.start >= self.range1.start && self.range2.end <= self.range1.end)
-    }
-
-    fn self_overlaps(&self) -> bool {
-        self.self_contains() || self.range2.contains(&self.range1.start) || self.range2.contains(&(self.range1.end - 1))
-    }
-
-}
-
-impl FromStr for RangePair {
-    type Err = ParseIntError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let ((r1_start, r1_end), (r2_start, r2_end)) = s.split_once(",")
-                                                        .and_then(|(r1, r2)| r1.split_once("-").zip(r2.split_once("-")))
-                                                        .unwrap();
-        let r1_start = r1_start.parse::<u64>()?;
-        let r1_end = r1_end.parse::<u64>()?;
-        let r2_start = r2_start.parse::<u64>()?;
-        let r2_end = r2_end.parse::<u64>()?;
-
-        Ok(RangePair {
-            range1: Range { start: r1_start, end: r1_end + 1 },
-            range2: Range { start: r2_start, end: r2_end + 1 }
-        })
-    }
-}
-
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let input = input.iter()
-                     .map(String::as_str)
-                     .map(str::parse::<RangePair>)
-                     .map(Result::unwrap);
-    let self_contains_count = input
-        .clone()
-        .map(|rp| rp.self_contains())
-        .filter(|b| *b)
-        .count();
-    let self_overlaps_count = input
-        .map(|rp| rp.self_overlaps())
-        .filter(|b| *b)
-        .count();
-    Ok([Some(self_contains_count.to_string()), Some(self_overlaps_count.to_string())])
+    let id_parser = preceded(tuple((tag("Card"), multispace1)), terminated(u32_parser, tuple((tag(":"), multispace1))));
+    let numbers_parser_gen = || separated_list0(multispace1, u32_parser);
+    let mut card_parser = all_consuming(tuple((
+        id_parser, 
+        terminated(
+            numbers_parser_gen(), 
+            tuple((tag(" |"), multispace1))), 
+        numbers_parser_gen())));
+        
+    let mut copy_counter = vec![];
+    let mut sum = 0;
+    for line in input.iter() {
+        let (_, (id, win_numbers, my_numbers)) = card_parser(line.as_str())
+            .finish()
+            .map_err(|e| convert_error(line.as_str(), e))?;
+        let win_numbers = HashSet::<_>::from_iter(win_numbers.into_iter());
+        let my_numbers = HashSet::<_>::from_iter(my_numbers.into_iter());
+        
+        let id = id as usize;
+        let win_count = win_numbers.intersection(&my_numbers).count();
+        copy_counter.resize(copy_counter.len().max(id+win_count), 1);
+        let copy_count = copy_counter[id - 1];
+        if win_count > 0 {
+            sum += 1 << (win_count - 1);
+            
+            for id in id..id+win_count {
+                copy_counter[id] += copy_count;
+            }
+        }
+    }
+            
+
+    Ok([Some(sum.to_string()), Some(copy_counter.into_iter().sum::<u32>().to_string())])
 }
 
 #[test]

+ 124 - 53
src/bin/day5.rs

@@ -1,4 +1,12 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use nom::sequence::{preceded, terminated, tuple};
+use nom::bytes::complete::tag;
+use nom::character::complete::{u32 as u32_parser, alpha1};
+use nom::error::convert_error;
+use nom::multi::separated_list0;
+use nom::combinator::{all_consuming, eof};
+use nom::Finish;
+use std::mem;
 
 const DAY: &str = "day5";
 
@@ -7,67 +15,130 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 }
 
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut stacks: Vec<Vec<u8>> = vec![];
+    let mut seeds_parser = all_consuming(preceded(tag("seeds: "), separated_list0(tag(" "), u32_parser)));
+    let mut seeds_parser = |line| seeds_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+        
+    let mut map_parser = all_consuming(tuple((terminated(alpha1, tag("-to-")), terminated(alpha1, tag(" map:")))));
+    let mut map_parser = |line| map_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+        
+    let mut range_parser = all_consuming(tuple((terminated(u32_parser, tag(" ")), terminated(u32_parser, tag(" ")), u32_parser)));
+    let mut range_parser = |line| range_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+        
+    let empty_parser = eof;
+    let empty_parser = |line| empty_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+    
     let mut input_iter = input.into_iter();
-    let mut operations: Vec<(usize, usize, usize)> = vec![];
-    'line: loop {
-        let line = input_iter.next();
-        if line.is_none() {
-            break
+    let mut line = input_iter.next();
+    
+    let (_, seed_list) = seeds_parser(line.expect("First line").as_str())?;
+    
+    let mut new_seeds = seed_list.iter().map(|s| Some(*s)).collect::<Vec<_>>();
+    let mut seeds = Vec::with_capacity(new_seeds.len());
+    seeds.resize(new_seeds.len(), None);
+    
+    let mut new_seed_ranges = seed_list.chunks_exact(2)
+        .map(|a| [a[0], a[1]])
+        .map(|[start_x, length]| Some(start_x..(start_x).saturating_add(length))).collect::<Vec<_>>();
+    let mut seed_ranges = Vec::with_capacity(new_seed_ranges.len());
+    seed_ranges.resize(new_seed_ranges.len(), None);
+    
+    let apply_map = |from, to, seed: u32| if to > from {
+            seed.saturating_add(to - from)
+        } else {
+            seed.saturating_sub(from - to)
+        };
+        
+    loop {
+        line = input_iter.next();
+        if line == None {
+            break;
         }
-        let line = line.unwrap();
-        for (container, i) in line.as_bytes().chunks(4).zip(0..) {
-            if i >= stacks.len() {
-                stacks.push(vec![]);
+        let line = line.unwrap().as_str();
+        if let Ok(_) = empty_parser(line) {
+            continue;
+        } else if let Ok((_,(from, to))) = map_parser(line) {
+            for (i, seed_cell) in seeds.iter_mut().enumerate() {
+                if let Some(_) = seed_cell {
+                    mem::swap(&mut new_seeds[i], seed_cell);
+                }
             }
-            match container {
-                &[b' ', b' ', b' ', b' '] => (),
-                &[b'[', c,    b']'] | &[b'[', c,    b']', b' '] => stacks[i].insert(0, c),
-                &[_, b'1', _, _]          => break 'line,
-                _ => eprintln!("Unknown container {:?}", container)
+            mem::swap(&mut seeds, &mut new_seeds);
+            
+            for (i, seed_range) in seed_ranges.iter_mut().enumerate() {
+                if let Some(_) = seed_range {
+                    mem::swap(&mut new_seed_ranges[i], seed_range);
+                }
+            }
+            mem::swap(&mut seed_ranges, &mut new_seed_ranges);
+//             println!("{:?}", seed_ranges);
+//             println!("{} -> {}", from, to);
+        } else if let Ok((_, (to, from, count))) = range_parser(line) {
+            let map_range = from..from.saturating_add(count);
+//             println!("{:?} -> {:?}", map_range, to..to.saturating_add(count));
+            for (i, seed_cell) in seeds.iter_mut().enumerate() {
+                if let Some(seed) = seed_cell {
+                    if map_range.contains(seed) {
+                        let new_seed = apply_map(from, to, *seed);
+                        let _  = seed_cell.take();
+                        new_seeds[i] = Some(new_seed);
+                    }
+                }
+            }
+            
+            let mut i = 0;
+            while i < seed_ranges.len() {
+                let seed_range = &mut seed_ranges[i];
+                if let Some(range) = seed_range {
+                    let intersection = range.start.max(map_range.start)..range.end.min(map_range.end);
+                    
+                    if !intersection.is_empty() {
+                        let preamble = range.start..map_range.start.min(range.end);
+                        let postamble = map_range.end.max(range.start)..range.end;
+                        
+                        range.start = apply_map(from, to, intersection.start);
+                        range.end = apply_map(from, to, intersection.end);
+                        mem::swap(&mut new_seed_ranges[i], seed_range);
+                        
+                        if !preamble.is_empty() {
+                            seed_ranges.push(Some(preamble));
+                            new_seed_ranges.push(None);
+                        }
+                        if !postamble.is_empty() {
+                            seed_ranges.push(Some(postamble));
+                            new_seed_ranges.push(None);
+                        }
+                        
+                    }
+                }
+                i += 1;
             }
         }
     }
-
-    input_iter.next();
-
-    for line in input_iter {
-        let (amount, from, to) = line.strip_prefix("move ")
-            .and_then(|s| s.split_once(" from "))
-            .and_then(|(amount, s)| s.split_once(" to ")
-                      .map(|(from, to)| (amount, from, to)))
-            .ok_or_else(|| format!("Could not parse line {}", line))?;
-
-        let (amount, from, to) = (amount.parse::<usize>()?, from.parse::<usize>()? - 1, to.parse::<usize>()? - 1);
-        operations.push((amount, from, to))
-    }
-
-    let mut stacks1 = stacks.clone();
-
-    for (amount, from, to) in operations.iter() {
-        let from_stack = stacks1.get_mut(*from).ok_or_else(|| format!("From stack does not exist in operation {:?}", (amount, from, to)))?;
-
-        let picked_up: Vec<_> = from_stack.drain((from_stack.len() - amount)..).collect();
-
-        let to_stack = stacks1.get_mut(*to).ok_or_else(|| format!("To stack does not exist in operation {:?}", (amount, from, to)))?;
-        to_stack.extend(picked_up.iter().rev());
+    
+    for (i, seed_cell) in seeds.iter_mut().enumerate() {
+        if let Some(_) = seed_cell {
+            mem::swap(&mut new_seeds[i], seed_cell);
+        }
     }
-    let message1: String = stacks1.into_iter().map(|mut s| s.pop().unwrap() as char).collect();
-
-    let mut stacks2 = stacks;
-
-    for (amount, from, to) in operations.iter() {
-        let from_stack = stacks2.get_mut(*from).ok_or_else(|| format!("From stack does not exist in operation {:?}", (amount, from, to)))?;
-
-        let picked_up: Vec<_> = from_stack.drain((from_stack.len() - amount)..).collect();
-
-        let to_stack = stacks2.get_mut(*to).ok_or_else(|| format!("To stack does not exist in operation {:?}", (amount, from, to)))?;
-        to_stack.extend(picked_up);
+    
+    for (i, seed_range) in seed_ranges.iter_mut().enumerate() {
+        if let Some(_) = seed_range {
+            mem::swap(&mut new_seed_ranges[i], seed_range);
+        }
     }
 
-    let message2: String = stacks2.into_iter().map(|mut s| s.pop().unwrap() as char).collect();
-
-    Ok([Some(message1), Some(message2)])
+    let lowest_location = new_seeds.into_iter().map(|seed| seed.unwrap()).min().unwrap();
+    let lowest_range_location = new_seed_ranges.into_iter().map(|seed_range| seed_range.unwrap().start).min().unwrap();
+    
+    Ok([Some(lowest_location.to_string()), Some(lowest_range_location.to_string())])
 }
 
 #[test]

+ 46 - 39
src/bin/day6.rs

@@ -1,6 +1,11 @@
-use std::collections::HashMap;
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use nom::combinator::all_consuming;
+use nom::sequence::{preceded, tuple};
+use nom::bytes::complete::{tag, take};
+use nom::character::complete::{u64 as u64_parser, alpha1, multispace1};
+use nom::multi::{separated_list0, fold_many0};
+use nom::error::convert_error;
+use nom::Finish;
 
 const DAY: &str = "day6";
 
@@ -8,44 +13,46 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-#[inline]
-fn all_unique(c1: u8, c2: u8, c3: u8, c4: u8) -> bool {
-    c1 != c2 && c1 != c3 && c1 != c4 && c2 != c3 && c2 != c4 && c3 != c4
-}
-
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let s = input.get(0).ok_or("Empty imput")?;
-    let mut buf: [u8; 4] = [0,0,0,0];
-    let mut buf_i = 0;
-    let mut res = None;
-    for (c_i, c) in s.bytes().enumerate() {
-        if c_i >= 3 && all_unique(c, buf[(buf_i + 1) % 4], buf[(buf_i + 2) % 4], buf[(buf_i + 3) % 4]) {
-            res = Some(c_i + 1);
-            break
-        }
-        buf[buf_i] = c;
-        buf_i += 1;
-        buf_i %= 4;
+    let mut numbers_parser = all_consuming(preceded(tuple((alpha1, tag(":"), multispace1)), separated_list0(multispace1, u64_parser)));
+    let mut numbers_parser = |line| numbers_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+        
+    let mut number_parser = all_consuming(preceded(
+        tuple((alpha1, tag(":"), multispace1)), 
+        fold_many0(take(1usize), || 0, |acc: u64, digit: &str| if digit != " " { 
+                digit.parse::<u64>().unwrap() + acc * 10
+            } else {
+                acc
+            })));
+    let mut number_parser = |line| number_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+
+    let (_, times) = numbers_parser(input[0].as_str()).unwrap();
+    let (_, distances) = numbers_parser(input[1].as_str()).unwrap();
+    
+    let (_, time) = number_parser(input[0].as_str()).unwrap();
+    let (_, distance) = number_parser(input[1].as_str()).unwrap();
+    
+    let calc_margin = |time, distance| {
+        let time = time as f64;
+        let distance = distance as f64;
+        let square_root_discriminator = (time*time - 4f64 * distance).sqrt();
+        let lower_bound = ((time - square_root_discriminator) / 2f64) + 10f64 * f64::EPSILON;
+        let upper_bound = ((time + square_root_discriminator) / 2f64) - 10f64 * f64::EPSILON;
+        (upper_bound.floor() - lower_bound.ceil() + 1f64) as u64
+    };
+    
+    let mut mul = 1;
+    for (time, distance) in times.into_iter().zip(distances.into_iter()) {
+        let margin = calc_margin(time, distance);
+        println!("{}", margin);
+        mul *= margin;
     }
-    let res1 = res.ok_or("No marker found")?;
-
-    let mut hashbuf: HashMap<u8, usize> = HashMap::new();
-    let mut res = None;
-    for (c_i, c) in s.bytes().enumerate() {
-        *hashbuf.entry(c).or_default() += 1;
-        if c_i >= 13 {
-            if hashbuf.values().all(|v| *v <= 1) {
-                res = Some(c_i + 1);
-                break;
-            }
-            let remove_key = s.as_bytes().get(c_i - 13).unwrap();
-            *hashbuf.get_mut(remove_key).unwrap() -= 1;
-        }
-    }
-
-    let res2 = res.ok_or("No message marker found")?;
-
-    Ok([Some(res1.to_string()), Some(res2.to_string())])
+    
+    Ok([Some(mul.to_string()), Some(calc_margin(time, distance).to_string())])
 }
 
 #[test]

+ 100 - 39
src/bin/day7.rs

@@ -1,4 +1,5 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use std::collections::HashMap;
 
 const DAY: &str = "day7";
 
@@ -6,54 +7,114 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
+const EMPTY_COUNTS: [u8; 14] = [0; 14];
 
-pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut stack: Vec<u64> = vec![];
-    let mut wd = 0;
-    let mut small_sum = 0;
-    let mut dir_sizes: Vec<u64> = vec![];
-    for line in input {
-        if line.starts_with("$ cd /") || line.starts_with("$ ls") || line.starts_with("dir") {
-            continue;
-        } else if line.starts_with("$ cd ..") {
-            if wd <= 100000 {
-                small_sum += wd;
-            }
-            dir_sizes.push(wd);
-            wd += stack.pop().ok_or("tried to cd .. at empty stack")?;
-        } else if line.starts_with("$ cd ") {
-            // Assume that we enter ever dir only once
-            stack.push(wd);
-            wd = 0;
-        } else {
-            let (size, _) = line.split_once(" ").ok_or_else(|| format!("Expected {{nr}} {{name}}, got {}", line))?;
-            wd += size.parse::<u64>()?;
+fn to_number(c: char) -> u8 {
+    if let Some(n) = c.to_digit(10) {
+        n as u8
+    } else {
+        match c {
+            'A' => 14,
+            'K' => 13,
+            'Q' => 12,
+            'J' => 11,
+            'T' => 10,
+            _ => panic!("Unknown card {}", c)
         }
     }
-    let root_size;
-    loop {
-        if wd <= 100000 {
-            small_sum += wd;
-        }
-        dir_sizes.push(wd);
-        if let Some(dir) = stack.pop() {
-            wd += dir;
-        } else {
-            root_size = wd;
-            break;
+}
+
+
+fn to_number_joker(c: char) -> u8 {
+    if let Some(n) = c.to_digit(10) {
+        n as u8
+    } else {
+        match c {
+            'A' => 14,
+            'K' => 13,
+            'Q' => 12,
+            'J' =>  1,
+            'T' => 10,
+            _ => panic!("Unknown card {}", c)
         }
     }
+}
+
+fn encode(hand: Vec<u8>) -> (Type, [u8; 5]) {
+    if let [c1, c2, c3, c4, c5] = &hand[..] {
+        (Type::determine(&hand), [*c1, *c2, *c3, *c4, *c5])
+    } else {
+        panic!("Hand {:?} does not have 5 cards", hand);
+    }
+}
 
-    let excess = 30000000 - (70000000 - root_size);
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
+enum Type {
+    FiveOfAKind = 7, FourOfAKind = 6, FullHouse = 5, ThreeOfAKind = 4, TwoPair = 3, OnePair = 2, HighCard = 1
+}
 
-    let mut min = u64::MAX;
-    for dir in dir_sizes {
-        if dir > excess {
-            min = min.min(dir);
+impl Type {
+    fn determine(hand: &[u8]) -> Type {
+        let mut counts = Vec::from(EMPTY_COUNTS);
+        for card in hand.iter() {
+            counts[*card as usize - 1] += 1;
+        }
+        
+        let [max1, max2] = counts[1..].iter().fold([0, 0], |[max1, max2], count| if max1 < *count {
+            [*count, max1]
+        } else if max2 < *count {
+            [max1, *count]
+        } else {
+            [max1, max2]
+        });
+        
+        let joker_count = counts[0];
+        
+        match [max1, max2, joker_count] {
+            [5, 0, 0] => Type::FiveOfAKind,
+            [4, 0, 1] => Type::FiveOfAKind,
+            [3, 0, 2] => Type::FiveOfAKind,
+            [2, 0, 3] => Type::FiveOfAKind,
+            [1, 0, 4] => Type::FiveOfAKind,
+            [0, 0, 5] => Type::FiveOfAKind,
+            [4, 1, 0] => Type::FourOfAKind,
+            [3, 1, 1] => Type::FourOfAKind,
+            [2, 1, 2] => Type::FourOfAKind,
+            [1, 1, 3] => Type::FourOfAKind,
+            [3, 2, 0] => Type::FullHouse,
+            [2, 2, 1] => Type::FullHouse,
+            [3, 1, 0] => Type::ThreeOfAKind,
+            [2, 1, 1] => Type::ThreeOfAKind,
+            [1, 1, 2] => Type::ThreeOfAKind,
+            [2, 2, 0] => Type::TwoPair,
+            [2, 1, 0] => Type::OnePair,
+            [1, 1, 1] => Type::OnePair,
+            [1, 1, 0] => Type::HighCard,
+            _ => panic!("Invalid hand counts: {:?}", [max1, max2])
         }
     }
+}
+
+pub fn run(input: &Vec<String>) -> AOCResult {
+    let mut hands = Vec::new();
+    let mut joker_hands = Vec::new();
+    for line in input.into_iter() {
+        let (hand_str, bid) = line.split_once(' ').unwrap();
+        let bid = bid.parse::<u32>().unwrap();
+        
+        let hand = encode(hand_str.chars().map(|c| to_number(c)).collect());
+        let joker_hand = encode(hand_str.chars().map(|c| to_number_joker(c)).collect());
+        
+        hands.push((hand, bid));
+        joker_hands.push((joker_hand, bid));
+    }
+    hands.sort();
+    joker_hands.sort();
+    
+    let winnings = hands.into_iter().enumerate().fold(0, |sum, (i, (_, bid))| sum + (i as u32 + 1) * bid);
+    let joker_winnings = joker_hands.into_iter().enumerate().fold(0, |sum, (i, (_, bid))| sum + (i as u32 + 1) * bid);
 
-    Ok([Some(small_sum.to_string()), Some(min.to_string())])
+    Ok([Some(winnings.to_string()), Some(joker_winnings.to_string())])
 }
 
 #[test]

+ 141 - 39
src/bin/day8.rs

@@ -1,60 +1,162 @@
-use std::collections::HashMap;
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
+use nom::combinator::all_consuming;
+use nom::sequence::{terminated, tuple};
+use nom::bytes::complete::tag;
+use nom::character::complete::alpha1;
+use nom::error::convert_error;
+use nom::Finish;
+use std::collections::{HashSet, HashMap};
 
 const DAY: &str = "day8";
+const BYTE_OFFSET: u8 = b'A';
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
+fn to_number(s: &str) -> u16 {
+    if let [b1, b2, b3] = s.as_bytes() {
+//         println!("{} {} {}", b1, b2, b3);
+        ((*b1 - BYTE_OFFSET) as u16) * 26 * 26 + ((*b2 - BYTE_OFFSET) as u16) * 26 + ((*b3 - BYTE_OFFSET) as u16) 
+    } else {
+        panic!("Identifier is not 3 chars: {}", s)
+    }
+}
+
+fn bezout_identity(a: i128, b: i128) -> (i128, i128, i128) {
+    let mut r0 = a;
+    let mut r1 = b;
+    let mut s0 = 1;
+    let mut t0 = 0;
+    let mut s1 = 0;
+    let mut t1 = 1;
+    
+    while r1 != 0 {
+        let q = r0 / r1;
+        (r0, r1) = (r1, r0 - q * r1);
+        (s0, s1) = (s1, s0 - q * s1);
+        (t0, t1) = (t1, t0 - q * t1);
+    }
+    
+    (s0, t0, r0)
+}
 
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut map: HashMap<(usize, usize), u32> = HashMap::new();
-    for (y, line) in input.into_iter().enumerate() {
-        for (x, height) in line.chars().enumerate() {
-            map.insert((x,y), height.to_digit(10).ok_or_else(|| format!("Unexpected char {} at {}:{}", height, y, x))?);
+    let mut node_parser = all_consuming(tuple((terminated(alpha1, tag(" = (")), terminated(alpha1, tag(", ")), terminated(alpha1, tag(")")))));
+    let mut node_parser = |line| node_parser(line)
+        .finish()
+        .map_err(|e| convert_error(line, e));
+
+    let mut graph = Vec::with_capacity(26usize.pow(3));
+    graph.resize(26usize.pow(3), [0, 0]);
+    let directions = input[0].clone();
+    
+    let end = to_number("ZZZ") as usize;
+    let mut ghost_starts = Vec::new();
+    let mut ghost_ends = HashSet::new();
+    let mut has_human_end = false;
+    
+    for node in input[2..].iter() {
+        let (_, (node, left, right)) = node_parser(node.as_str())?;
+//         println!("{} {} {}", to_number(node), to_number(left), to_number(right));
+        let node = to_number(node) as usize;
+        graph[node] = [to_number(left), to_number(right)];
+        match node % 26 {
+            0 => {ghost_starts.push(node);},
+            25 => {ghost_ends.insert(node);},
+            _ => ()
+        };
+        if node == end {
+            has_human_end = true
         }
     }
+    
+    let count = if has_human_end {
+        let mut node = to_number("AAA") as usize;
+        let mut count = 1;
+        for direction in directions.chars().cycle() {
+            match direction {
+                'L' => { node = graph[node][0] as usize; }
+                'R' => { node = graph[node][1] as usize; }
+                _ => return Err(format!("Unexpected direction {}", direction).into())
+            }
+            
+            if node == end {
+                break
+            }
+            count += 1;
+        }
+        Some(count)
+    } else {
+        None
+    };
+    
+    let mut graph_descriptions = Vec::new();
 
-    let (max_x, max_y) = *map.keys().max().ok_or("Empty map")?;
-    let is_edge = |x: usize, y: usize| x == 0 || y == 0 || x == max_x || y == max_y;
-
-
-    let mut visible_count = 0;
-    let mut max_scenic = 1;
-    for (coord, height) in map.iter() {
-        let ((x, y), height) = (*coord, *height);
-        let mut visible = false;
-        let mut scenic = 1;
-        for (dx, dy) in [(0,1),(1,0),(0,-1),(-1,0)] {
-            let (mut x, mut y) = (x, y);
-            let mut viewing_distance = 0;
-            loop {
-                if is_edge(x, y) {
-                    // println!("{:?} ✔️", (x,y));
-                    visible = true;
-                    break;
-                }
-                viewing_distance += 1;
-                x = ((x as isize) + dx) as usize;
-                y = ((y as isize) + dy) as usize;
-                let current_height = *map.get(&(x,y)).ok_or_else(|| format!("No map entry at {}:{}", y, x))?;
-                // println!("{} <=? {} @ {:?}->{:?}", current_height, height, (dx, dy), (x,y));
-                if height <= current_height {
-                    // println!("{:?} ✖️", (x,y));
-                    break;
+    for node in ghost_starts.into_iter() {
+        let mut node = node;
+        let mut step_counter = HashMap::new();
+        let mut ghost_end = None;
+        let mut count: u128 = 0;
+        let mut loop_start = None;
+        for (i, direction) in directions.chars().enumerate().cycle() {
+            if step_counter.contains_key(&(i, node)) {
+                loop_start = Some((i, node));
+                break;
+            }
+            step_counter.insert((i, node), count);
+            match direction {
+                'L' => { node = graph[node][0] as usize; }
+                'R' => { node = graph[node][1] as usize; }
+                _ => return Err(format!("Unexpected direction {}", direction).into())
+            }
+            
+            count += 1;
+            if ghost_ends.contains(&node) {
+                if let Some(_)  = ghost_end {
+                    return Err(format!("Only one ghost end per ghost path supported").into());
                 }
+                ghost_end = Some(count);
             }
-            scenic *= viewing_distance;
         }
-        if visible {
-            visible_count += 1;
+        let loop_start = *step_counter.get(&loop_start.unwrap()).unwrap();
+        let ghost_end = ghost_end.unwrap();
+        if ghost_end < loop_start {
+            return Err(format!("ghost end in preamble is not supported").into());
         }
-        max_scenic = max_scenic.max(scenic);
+        let loop_size = count - loop_start;
+        graph_descriptions.push(loop_size);
     }
+    
+    println!("{:?}", graph_descriptions);
+    
+    let res = graph_descriptions.into_iter().reduce(|a,b| a*b/(bezout_identity(a as i128,b as i128).2 as u128)).unwrap();
+    
+//     let (mut loop_size, mut end) = graph_descriptions.next().unwrap();
+//     while let Some((next_loop_size, next_end)) = graph_descriptions.next() {
+//         println!("{} {}", loop_size, next_loop_size);
+//         let (c0, c1, gcd) = bezout_identity(loop_size, next_loop_size);
+//         let (c0, c1, gcd) = if end <= next_end {
+//             (c0 % (next_loop_size / gcd), (-c1) % (loop_size / gcd), gcd)
+//         } else {
+//             ((-c0) % (next_loop_size / gcd), c1 % (loop_size / gcd), gcd)
+//         };
+//         
+//         let diff = end.abs_diff(next_end) as i128;
+//         let extra_loops = (c0 * diff / loop_size).min(c1 * diff / next_loop_size);
+//         let new_loop_size = loop_size * next_loop_size / gcd;
+//         println!("{} {} {}", new_loop_size, c0, extra_loops);
+//         end += c0 * diff * (loop_size / gcd) - new_loop_size * extra_loops;
+//         loop_size = new_loop_size;
+//     }
+    
+    println!("{}", res);
+    
+    /*
+    println!("{:?}", Vec::from(&graph[..10]));
+    println!("{:?} {:?} {:?}", graph[0], graph[to_number("BBB") as usize], graph[to_number("CCC") as usize]);*/
 
-    Ok([Some(visible_count.to_string()), Some(max_scenic.to_string())])
+    Ok([count.map(|c| c.to_string()), Some(res.to_string())])
 }
 
 #[test]

+ 23 - 69
src/bin/day9.rs

@@ -1,6 +1,4 @@
-use std::collections::HashSet;
-
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
 
 const DAY: &str = "day9";
 
@@ -8,77 +6,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     common::run(DAY, &run)
 }
 
-const R: [i64;4]= [1,0,0,1];
-const U: [i64;4]= [0,-1,1,0];
-const L: [i64;4]= [-1,0,0,-1];
-const D: [i64;4]= [0,1,-1,0];
-
-
-#[inline]
-fn rotate(vec: [i64; 2], dir: [i64;4]) -> [i64; 2] {
-    [vec[0]*dir[0] + vec[1]*dir[1], vec[0]*dir[2] + vec[1]*dir[3]]
-}
-
-#[inline]
-fn subtract(vec1: [i64; 2], vec2: [i64; 2]) -> [i64; 2] {
-    [vec1[0]-vec2[0], vec1[1]-vec2[1]]
-}
-
-#[inline]
-fn add(vec1: [i64; 2], vec2: [i64; 2]) -> [i64; 2] {
-    [vec1[0]+vec2[0], vec1[1]+vec2[1]]
-}
-
-const LENGTH: usize = 10;
-
 pub fn run(input: &Vec<String>) -> AOCResult {
-    let mut visited2: HashSet<[i64;2]> = HashSet::new();
-    let mut visited10: HashSet<[i64;2]> = HashSet::new();
-    let mut rope = [[0i64,0i64];LENGTH];
-    visited2.insert([0,0]);
-    visited10.insert([0,0]);
-    for line in input {
-        let (dir, count) = line.split_once(" ").ok_or("Line with no space encountered")?;
-        let count = count.parse::<i64>()?;
-        let (dir, r_dir) = match dir {
-            "R" => Ok((R, R)),
-            "U" => Ok((U, D)),
-            "L" => Ok((L, L)),
-            "D" => Ok((D, U)),
-            c => Err(format!("Unknown direction {}", c)),
-        }?;
-        for _ in 0..count {
-            rope[0] = add(rope[0], rotate([1, 0], r_dir));
-            for i in 0..LENGTH-1 {
-                let head = &rope[i];
-                let next = &rope[i+1];
-
-                let dif = subtract(*head, *next);
-                let next_delta = match dif {
-                    [ 2,  0]                        => [ 1,  0],
-                    [-2,  0]                        => [-1,  0],
-                    [ 0,  2]                        => [ 0,  1],
-                    [ 0, -2]                        => [ 0, -1],
-                    [ 1,  2] | [ 2,  2] | [ 2,  1]  => [ 1,  1],
-                    [ 2, -1] | [ 2, -2] | [ 1, -2]  => [ 1, -1],
-                    [-1, -2] | [-2, -2] | [-2, -1]  => [-1, -1],
-                    [-2,  1] | [-2,  2] | [-1,  2]  => [-1,  1],
-                    [_, _] => [0, 0],
-                };
-                let next = add(*next, next_delta);
-                match i {
-                    0 => {visited2.insert(next);},
-                    8 => {visited10.insert(next);},
-                    _ => ()
-                }
-                // println!("{}: N: {:?}->{:?}, H: {:?} dif: {:?}, td: {:?}", line, rope[i+1], next, head, dif, next_delta);
-                rope[i+1] = next;
+    let mut right_sum: isize = 0;
+    let mut left_sum: isize = 0;
+    for line in input.into_iter() {
+        let mut series = line.split(' ').map(str::parse::<isize>).collect::<Result<Vec<_>, _>>()?;
+        let mut right_sum_list = vec![];
+        let mut left_sum_list = vec![];
+        while series.len() > 1 && !series.iter().all(|&x| x == 0) {
+            left_sum_list.push(series[0]);
+            for i in 0..(series.len() - 1) {
+                series[i] = series[i + 1] - series[i];
             }
-            // println!("R: {:?}", rope);
+            right_sum_list.push(series.pop().unwrap());
+        }
+        println!("{:?}", line);
+        println!("{:?}", series);
+        println!("{:?}", right_sum_list);
+        println!("{:?} {}", left_sum_list, left_sum_list.iter().rev().fold(0, |acc, n| n - acc));
+        if series[0] != 0 {
+            return Err(format!("Series did not converge to 0's {}", line).into());
         }
+        right_sum += right_sum_list.into_iter().sum::<isize>();
+        left_sum += left_sum_list.iter().rev().fold(0, |acc, n| n - acc);
     }
+    
 
-    Ok([Some(visited2.len().to_string()), Some(visited10.len().to_string())])
+    Ok([Some(right_sum.to_string()), Some(left_sum.to_string())])
 }
 
 #[test]

+ 16 - 12
src/common.rs

@@ -23,6 +23,10 @@ fn read_lines<P>(filename: P) -> io::Result<Vec<String>>
     return lines.collect();
 }
 
+fn pretty_print_iter(vec: &[String]) -> String {
+    vec.iter().fold(String::new(), |acc, s| acc + &s + "\n")
+}
+
 pub fn run<F: AOCProgram>(day: &str, prog: &F) -> Result<(), Box<dyn Error>> {
     let arguments = std::env::args();
     let arguments = arguments::parse(arguments)?;
@@ -36,7 +40,7 @@ pub fn run<F: AOCProgram>(day: &str, prog: &F) -> Result<(), Box<dyn Error>> {
 
     let output: Result<String, Box<dyn Error>> = match result {
         Err(e) => {
-            let res = format!("Failed on input {:?} with error {:?}", &input, e).into();
+            let res = format!("Failed on input\"\n{}\" with error\n{}", pretty_print_iter(&input), e).into();
             return Err(res);
         }
         Ok(result) =>
@@ -76,15 +80,15 @@ pub fn run_prog<F: AOCProgram>(day: &str, prog: &F) {
     let inputs = files_to_vec(day, "./inputs");
 
     if let Err(e) = inputs {
-        eprintln!("Error occurred while reading input files {:?}", e);
+        eprintln!("Error occurred while reading input files {}", e);
         return;
     }
     let inputs = inputs.unwrap();
-
+    
     for (path, input) in inputs {
         let result = prog.run(&input);
         match result {
-            Err(e) => println!("{}: Failed on input {:?} with error {:?}", path, &input, e),
+            Err(e) => println!("{}: Failed on input\"\n{}\" with error\n{}", path, pretty_print_iter(&input), e),
             Ok(result) => match &result {
                 [Some(o1), Some(o2)] => println!("{}:\n\t1:{}\n\t2:{}", path, o1, o2),
                 [Some(o1), None] => println!("{}:\n\t1:{}", path, o1),
@@ -102,7 +106,7 @@ pub fn run_test<F: AOCProgram>(day: &str, prog: &F) -> bool {
     let inputs = files_to_vec(day, "./test_inputs");
 
     if let Err(e) = inputs {
-        eprintln!("Error occurred while reading test input files {:?}", e);
+        eprintln!("Error occurred while reading test input files {}", e);
         return false;
     }
     let inputs = inputs.unwrap();
@@ -136,8 +140,8 @@ pub fn run_test<F: AOCProgram>(day: &str, prog: &F) -> bool {
 fn test<F: AOCProgram>(prog: &F, input: &Vec<String>, expected: [Option<&str>; 2], path: &str) -> [Option<bool>; 2] {
     let result = prog.run(input);
     if result.is_err() {
-        println!("{}: Program failed on input {:?} with error {:?}",
-                 path, &input[..10.min(input.len())], result.unwrap_err());
+        println!("{}: Program failed on input\"\n{}\" with error\n{}",
+                 path, pretty_print_iter(&input[..10.min(input.len())]), result.unwrap_err());
         return [Some(false), Some(false)];
     }
     let result = &result.unwrap();
@@ -165,8 +169,8 @@ fn test<F: AOCProgram>(prog: &F, input: &Vec<String>, expected: [Option<&str>; 2
         if succeeded[0].unwrap() {
             println!("{}: Result 1 succeeded", path)
         } else {
-            eprintln!("{}: Result 1 failed: Wrong output for input {:?}.\n\tExpected \"{}\"\n\tReceived \"{}\"",
-                      path, &input[..10.min(input.len())], expected[0].as_ref().unwrap(), result[0].as_ref().unwrap());
+            eprintln!("{}: Result 1 failed: Wrong output for input \"\n{}\".\n\tExpected \"{}\"\n\tReceived \"{}\"",
+                      path, pretty_print_iter(&input[..10.min(input.len())]), expected[0].as_ref().unwrap(), result[0].as_ref().unwrap());
         }
     }
 
@@ -176,8 +180,8 @@ fn test<F: AOCProgram>(prog: &F, input: &Vec<String>, expected: [Option<&str>; 2
         if succeeded[1].unwrap() {
             println!("{}: Result 2 succeeded", path)
         } else {
-            eprintln!("{}: Result 2 failed: Wrong output for input {:?}.\n\tExpected {}\n\tReceived {}",
-                      path, &input[..10.min(input.len())], expected[1].as_ref().unwrap(), result[1].as_ref().unwrap());
+            eprintln!("{}: Result 2 failed: Wrong output for input \"\n{}\".\n\tExpected {}\n\tReceived {}",
+                      path, pretty_print_iter(&input[..10.min(input.len())]), expected[1].as_ref().unwrap(), result[1].as_ref().unwrap());
         }
     }
 
@@ -210,7 +214,7 @@ fn files_to_vec<'a>(day: &str, input_path: &str) -> Result<Vec<(String, Vec<Stri
         match r {
             Ok(None) => (),
             Ok(Some(v)) => acc.push(v),
-            Err(e) => eprintln!("Error while processing file: {:?}", e),
+            Err(e) => eprintln!("Error while processing file: {}", e),
         }
         return acc;
     });

+ 9 - 2
src/exec.rs

@@ -83,7 +83,8 @@ impl Runner {
                 "-q",
                 format!("test_day{}", self.day.day).as_str(),    // TODO specify which part to test
                 "--bin",
-                format!("day{}", self.day.day).as_str()
+                format!("day{}", self.day.day).as_str(),
+                "--color=always",
             ])
             .output()?;
         eprintln!("{}", String::from_utf8(output.stderr)?);
@@ -102,7 +103,8 @@ impl Runner {
                     "--bin", format!("day{}", self.day.day).as_str(),
                     "--",
                     "--part", self.part.as_digit().to_string().as_str(),
-                    "--output-file", self.day.output_file_path(&self.part).as_str()
+                    "--output-file", self.day.output_file_path(&self.part).as_str(),
+                    "--color=always",
                 ])
                 .output()?; // TODO Spawn and do fancy terminal things
             eprintln!("{}", String::from_utf8(output.stderr)?);
@@ -155,6 +157,10 @@ impl Runner {
         let data = format!("level={}&answer={}", self.part.as_digit(), submission);
         self.web_context.curl_post_to_lynx(self.day.submit_url().as_str(), data.as_bytes())
     }
+    
+    pub fn reset_part(&mut self) {
+        self.part = Part::ONE
+    }
 
     pub fn switch_part(&mut self) {
         match self.part {
@@ -228,6 +234,7 @@ impl Runner {
                         CommandType::Puzzle => Ok(self.web_context.lynx(self.day.puzzle_url())?),
                         CommandType::Day => Ok({
                             self.change_day(args)?;
+                            self.reset_part();
                             self.print_day();
                             self.make_missing_files_if_open()?;
                         }),

+ 1 - 1
src/web.rs

@@ -93,4 +93,4 @@ impl WebContext {
         self.lynx(path.to_str().unwrap().to_owned())?;
         return Ok(());
     }
-}
+}

+ 1 - 1
templates/day.rs.tpl

@@ -1,4 +1,4 @@
-use aoc2022_niels_overkamp::common::{self, AOCResult};
+use aoc2023_niels_overkamp::common::{self, AOCResult};
 
 const DAY: &str = "{{.day}}";
 

+ 8 - 0
test_inputs/day1-a.txt

@@ -0,0 +1,8 @@
+two1nine
+eightwothree
+abcone2threexyz
+xtwone3four
+4nineeightseven2
+zoneight234
+7pqrstsixteen
+$281

+ 29 - 0
test_inputs/day1-b.txt

@@ -0,0 +1,29 @@
+51591twosix4dhsxvgghxq
+425nine
+llvmhjtr8nbbhrfone
+lpbjvpbtdfvtxtdvkpjs7qrvddkzmjtlqtg
+3sixnineseven
+rfmsqbkms7three
+33291six
+oneonevstpxxrjpnine7six
+75sevennine14mzqljsjfbb7two
+three6two9jckvk
+zprj8394threehczfkncntk
+mkqtlrzmzfsix2ccqsnnxtwo4sevenxp9
+tdszrfzspthree2ttzseven5seven
+two3fiveckrsjr
+four14three7
+4fdkcclmxmxsevenfiver
+5sjnnfivefourzxxfpfivenine7five
+77sixfive
+twofivecrkvmpcpvzddvzcmjhjlthree8fcrrninefive
+fivetwo562five
+2ninekvdbfnmjmd6ninentpktmgseven92
+2twonine
+5fivesprm4ndqzbqnjqx
+7jfq9
+1hgnkmx91
+2jcrmhfvntc3lqnine4five4
+zvqmpjrpninejhqrnineggghcrjfd3onefive
+11three64qjjhqdnonetwo
+$1376

+ 5 - 15
test_inputs/day1.txt

@@ -1,15 +1,5 @@
-1000
-2000
-3000
-
-4000
-
-5000
-6000
-
-7000
-8000
-9000
-
-10000
-24000$45000
+1abc2
+pqr3stu8vwx
+a1b2c3d4e5f
+treb7uchet
+142

+ 11 - 0
test_inputs/day10-a.txt

@@ -0,0 +1,11 @@
+.F----7F7F7F7F-7....
+.|F--7||||||||FJ....
+.||.FJ||||||||L7....
+FJL7L7LJLJ||LJ.L-7..
+L--J.L7...LJS7F-7L7.
+....F-J..F7FJ|L7L7L7
+....L7.F7||L7|.L7L7|
+.....|FJLJ|FJ|F7|.LJ
+....FJL-7.||.||||...
+....L---J.LJ.LJLJ...
+$8

+ 11 - 0
test_inputs/day10-b.txt

@@ -0,0 +1,11 @@
+FF7FSF7F7F7F7F7F---7
+L|LJ||||||||||||F--J
+FL-7LJLJ||||||LJL-77
+F--JF--7||LJLJ7F7FJ-
+L---JF-JLJ.||-FJLJJ7
+|F|F-JF---7F7-L7L|7|
+|FFJF7L7F-JF7|JL---7
+7-L-JL7||F7|L7F-7F7|
+L.L7LFJ|||||FJL7||LJ
+L7JLJL-JLJLJL--JLJ.L
+$10

+ 10 - 0
test_inputs/day10-c.txt

@@ -0,0 +1,10 @@
+...........
+.S-------7.
+.|F-----7|.
+.||.....||.
+.||.....||.
+.|L-7.F-J|.
+.|..|.|..|.
+.L--J.L--J.
+...........
+$4

+ 6 - 147
test_inputs/day10.txt

@@ -1,147 +1,6 @@
-addx 15
-addx -11
-addx 6
-addx -3
-addx 5
-addx -1
-addx -8
-addx 13
-addx 4
-noop
-addx -1
-addx 5
-addx -1
-addx 5
-addx -1
-addx 5
-addx -1
-addx 5
-addx -1
-addx -35
-addx 1
-addx 24
-addx -19
-addx 1
-addx 16
-addx -11
-noop
-noop
-addx 21
-addx -15
-noop
-noop
-addx -3
-addx 9
-addx 1
-addx -3
-addx 8
-addx 1
-addx 5
-noop
-noop
-noop
-noop
-noop
-addx -36
-noop
-addx 1
-addx 7
-noop
-noop
-noop
-addx 2
-addx 6
-noop
-noop
-noop
-noop
-noop
-addx 1
-noop
-noop
-addx 7
-addx 1
-noop
-addx -13
-addx 13
-addx 7
-noop
-addx 1
-addx -33
-noop
-noop
-noop
-addx 2
-noop
-noop
-noop
-addx 8
-noop
-addx -1
-addx 2
-addx 1
-noop
-addx 17
-addx -9
-addx 1
-addx 1
-addx -3
-addx 11
-noop
-noop
-addx 1
-noop
-addx 1
-noop
-noop
-addx -13
-addx -19
-addx 1
-addx 3
-addx 26
-addx -30
-addx 12
-addx -1
-addx 3
-addx 1
-noop
-noop
-noop
-addx -9
-addx 18
-addx 1
-addx 2
-noop
-noop
-addx 9
-noop
-noop
-noop
-addx -1
-addx 2
-addx -37
-addx 1
-addx 3
-noop
-addx 15
-addx -21
-addx 22
-addx -6
-addx 1
-noop
-addx 2
-addx 1
-noop
-addx -10
-noop
-noop
-addx 20
-addx 1
-addx 2
-addx 2
-addx -6
-addx -11
-noop
-noop
-noop
-13140$##..##..##..##..##..##..##..##..##..##..###...###...###...###...###...###...###.####....####....####....####....####....#####.....#####.....#####.....#####.....######......######......######......###########.......#######.......#######.....
+..F7.
+.FJ|.
+SJ.L7
+|F--J
+LJ...
+8$1

+ 11 - 28
test_inputs/day11.txt

@@ -1,28 +1,11 @@
-Monkey 0:
-  Starting items: 79, 98
-  Operation: new = old * 19
-  Test: divisible by 23
-    If true: throw to monkey 2
-    If false: throw to monkey 3
-
-Monkey 1:
-  Starting items: 54, 65, 75, 74
-  Operation: new = old + 6
-  Test: divisible by 19
-    If true: throw to monkey 2
-    If false: throw to monkey 0
-
-Monkey 2:
-  Starting items: 79, 60, 97
-  Operation: new = old * old
-  Test: divisible by 13
-    If true: throw to monkey 1
-    If false: throw to monkey 3
-
-Monkey 3:
-  Starting items: 74
-  Operation: new = old + 3
-  Test: divisible by 17
-    If true: throw to monkey 0
-    If false: throw to monkey 1
-10605$2713310158
+...#......
+.......#..
+#.........
+..........
+......#...
+.#........
+.........#
+..........
+.......#..
+#...#.....
+374

+ 2 - 0
test_inputs/day12-a.txt

@@ -0,0 +1,2 @@
+???.### 1,1,3
+$1

+ 7 - 6
test_inputs/day12.txt

@@ -1,6 +1,7 @@
-Sabqponm
-abcryxxl
-accszExk
-acctuvwj
-abdefghi
-31$29
+???.### 1,1,3
+.??..??...?##. 1,1,3
+?#?#?#?#?#?#?#? 1,3,1,6
+????.#...#... 4,1,1
+????.######..#####. 1,6,5
+?###???????? 3,2,1
+21$525152

+ 54 - 0
test_inputs/day13-a.txt

@@ -0,0 +1,54 @@
+###.###..###.##
+..############.
+...#..####..#..
+..#.########.#.
+....##.##.##...
+..##.##..##.##.
+##.#........#.#
+...#..####..#..
+##.#..####..#.#
+##.##......##.#
+##...#.##.#...#
+.....#.#..#....
+#####.#..#.####
+##.#.##..##.#.#
+##..########..#
+##..#.####.#..#
+....#.#..#.#...
+
+##.###..#.#
+#..###..#.#
+.#...###.#.
+..##...#..#
+###.#..###.
+###.#..###.
+..##...#..#
+
+.#..##..#.#..#.#.
+.#..##..#......#.
+#..###...##..##..
+.##.##.##..##..##
+.#.####.#..##..#.
+..#....#..#..#..#
+.##.##.##.#..#.##
+#.#.##.#.#.##.#.#
+###....##########
+##.#..#.###..###.
+...#..#...#..#...
+#.##..##.#....#.#
+.##.##.##.#..#.##
+
+#..##...###
+#..##..#...
+#..##..#...
+#..##...###
+#####..#.##
+#.##..#####
+#..####.#.#
+.##.#.#.#..
+.....#####.
+####..#####
+#..#.#.###.
+#..#.#..#.#
+#..#.##....
+713$115

+ 15 - 23
test_inputs/day13.txt

@@ -1,24 +1,16 @@
-[1,1,3,1,1]
-[1,1,5,1,1]
+#.##..##.
+..#.##.#.
+##......#
+##......#
+..#.##.#.
+..##..##.
+#.#.##.#.
 
-[[1],[2,3,4]]
-[[1],4]
-
-[9]
-[[8,7,6]]
-
-[[4,4],4,4]
-[[4,4],4,4,4]
-
-[7,7,7,7]
-[7,7,7]
-
-[]
-[3]
-
-[[[]]]
-[[]]
-
-[1,[2,[3,[4,[5,6,7]]]],8,9]
-[1,[2,[3,[4,[5,6,0]]]],8,9]
-13$140
+#...##..#
+#....#..#
+..##..###
+#####.##.
+#####.##.
+..##..###
+#....#..#
+405$400

+ 11 - 3
test_inputs/day14.txt

@@ -1,3 +1,11 @@
-498,4 -> 498,6 -> 496,6
-503,4 -> 502,4 -> 502,9 -> 494,9
-24$93
+O....#....
+O.OO#....#
+.....##...
+OO.#O....O
+.O.....O#.
+O.#..O.#.#
+..O..#O..O
+.......O..
+#....###..
+#OO..#....
+136$64

+ 2 - 16
test_inputs/day15.txt

@@ -1,16 +1,2 @@
-Sensor at x=2, y=18: closest beacon is at x=-2, y=15
-Sensor at x=9, y=16: closest beacon is at x=10, y=16
-Sensor at x=13, y=2: closest beacon is at x=15, y=3
-Sensor at x=12, y=14: closest beacon is at x=10, y=16
-Sensor at x=10, y=20: closest beacon is at x=10, y=16
-Sensor at x=14, y=17: closest beacon is at x=10, y=16
-Sensor at x=8, y=7: closest beacon is at x=2, y=10
-Sensor at x=2, y=0: closest beacon is at x=2, y=10
-Sensor at x=0, y=11: closest beacon is at x=2, y=10
-Sensor at x=20, y=14: closest beacon is at x=25, y=17
-Sensor at x=17, y=20: closest beacon is at x=21, y=22
-Sensor at x=16, y=7: closest beacon is at x=15, y=3
-Sensor at x=14, y=3: closest beacon is at x=15, y=3
-Sensor at x=20, y=1: closest beacon is at x=15, y=3
-10
-26$291
+rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
+1320$145

+ 11 - 0
test_inputs/day16.txt

@@ -0,0 +1,11 @@
+.|...\....
+|.-.\.....
+.....|-...
+........|.
+..........
+.........\
+..../.\\..
+.-.-/..|..
+.|....-|.\
+..//.|....
+46$51

+ 14 - 0
test_inputs/day17.txt

@@ -0,0 +1,14 @@
+2413432311323
+3215453535623
+3255245654254
+3446585845452
+4546657867536
+1438598798454
+4457876987766
+3637877979653
+4654967986887
+4564679986453
+1224686865563
+2546548887735
+4322674655533
+102$94

+ 15 - 0
test_inputs/day18.txt

@@ -0,0 +1,15 @@
+R 6 (#70c710)
+D 5 (#0dc571)
+L 2 (#5713f0)
+D 2 (#d2c081)
+R 2 (#59c680)
+D 2 (#411b91)
+L 5 (#8ceee2)
+U 2 (#caa173)
+L 1 (#1b58a2)
+U 2 (#caa171)
+R 2 (#7807d2)
+U 3 (#a77fa3)
+L 2 (#015232)
+U 2 (#7a21e3)
+62

+ 0 - 0
test_inputs/day19.txt


+ 6 - 4
test_inputs/day2.txt

@@ -1,4 +1,6 @@
-A Y
-B X
-C Z
-15$12
+Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
+Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
+Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
+Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
+Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
+8$2286

+ 10 - 0
test_inputs/day3-a.txt

@@ -0,0 +1,10 @@
+100*010
+.1000..
+..10000
+.44..*.
+20000.2
+7777777
+.20.4..
+*....*.
+.40.1..
+31177$804

+ 11 - 7
test_inputs/day3.txt

@@ -1,7 +1,11 @@
-vJrwpWtwJgWrhcsFMMfFFhFp
-jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
-PmmdzqPrVvPwwTWBwg
-wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
-ttgJtRGJQctTZtZT
-CrZsJsPPZsGzwwsLwLmpwMDw
-157$70
+467..114..
+...*......
+..35..633.
+......#...
+617*......
+.....+.58.
+..592.....
+......755.
+...$.*....
+.664.598..
+4361$467835

+ 7 - 7
test_inputs/day4.txt

@@ -1,7 +1,7 @@
-2-4,6-8
-2-3,4-5
-5-7,7-9
-2-8,3-7
-6-6,4-6
-2-6,4-8
-2$4
+Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
+Card 2: 13 32 20 16 61 | 61 30   68 82 17 32 24 19
+Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
+Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
+Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
+Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
+13$30

+ 34 - 10
test_inputs/day5.txt

@@ -1,10 +1,34 @@
-    [D]
-[N] [C]
-[Z] [M] [P]
- 1   2   3
-
-move 1 from 2 to 1
-move 3 from 1 to 3
-move 2 from 2 to 1
-move 1 from 1 to 2
-CMZ$MCD
+seeds: 79 14 55 13
+
+seed-to-soil map:
+50 98 2
+52 50 48
+
+soil-to-fertilizer map:
+0 15 37
+37 52 2
+39 0 15
+
+fertilizer-to-water map:
+49 53 8
+0 11 42
+42 0 7
+57 7 4
+
+water-to-light map:
+88 18 7
+18 25 70
+
+light-to-temperature map:
+45 77 23
+81 45 19
+68 64 13
+
+temperature-to-humidity map:
+0 69 1
+1 0 69
+
+humidity-to-location map:
+60 56 37
+56 93 4
+35$46

+ 0 - 2
test_inputs/day6-1.txt

@@ -1,2 +0,0 @@
-bvwbjplbgvbhsrlpgdmjqwftvncz
-5$23

+ 0 - 2
test_inputs/day6-2.txt

@@ -1,2 +0,0 @@
-nppdvjthqldpwncqszvftbrmjlhg
-6$23

+ 0 - 2
test_inputs/day6-3.txt

@@ -1,2 +0,0 @@
-nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg
-10$29

+ 0 - 2
test_inputs/day6-4.txt

@@ -1,2 +0,0 @@
-zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw
-11$26

+ 3 - 2
test_inputs/day6.txt

@@ -1,2 +1,3 @@
-mjqjpqmgbljsphdztnvjfqwrcgsmlb
-7$19
+Time:      7  15   30
+Distance:  9  40  200
+288$71503

+ 6 - 24
test_inputs/day7.txt

@@ -1,24 +1,6 @@
-$ cd /
-$ ls
-dir a
-14848514 b.txt
-8504156 c.dat
-dir d
-$ cd a
-$ ls
-dir e
-29116 f
-2557 g
-62596 h.lst
-$ cd e
-$ ls
-584 i
-$ cd ..
-$ cd ..
-$ cd d
-$ ls
-4060174 j
-8033020 d.log
-5626152 d.ext
-7214296 k
-95437$24933642
+32T3K 765
+T55J5 684
+KK677 28
+KTJJT 220
+QQQJA 483
+6440$5905

+ 6 - 0
test_inputs/day8-a.txt

@@ -0,0 +1,6 @@
+LLR
+
+AAA = (BBB, BBB)
+BBB = (AAA, ZZZ)
+ZZZ = (ZZZ, ZZZ)
+6

+ 10 - 0
test_inputs/day8-b.txt

@@ -0,0 +1,10 @@
+LR
+
+AAA = (AAB, XXX)
+AAB = (XXX, AAZ)
+AAZ = (AAB, XXX)
+BBA = (BBB, XXX)
+BBB = (BBC, BBC)
+BBC = (BBZ, BBZ)
+BBZ = (BBB, BBB)
+XXX = (XXX, XXX)

+ 10 - 6
test_inputs/day8.txt

@@ -1,6 +1,10 @@
-30373
-25512
-65332
-33549
-35390
-21$8
+RL
+
+AAA = (BBB, CCC)
+BBB = (DDD, EEE)
+CCC = (ZZZ, GGG)
+DDD = (DDD, DDD)
+EEE = (EEE, EEE)
+GGG = (GGG, GGG)
+ZZZ = (ZZZ, ZZZ)
+2

+ 0 - 9
test_inputs/day9-1.txt

@@ -1,9 +0,0 @@
-R 5
-U 8
-L 8
-D 3
-R 17
-D 10
-L 25
-U 20
-$36

+ 6 - 9
test_inputs/day9.txt

@@ -1,9 +1,6 @@
-R 4
-U 4
-L 3
-D 1
-R 4
-D 1
-L 5
-R 2
-13$1
+0 3 6 9 12 15
+1 3 6 10 15 21
+10 13 16 21 30 45
+13 31 64 123 233 435 798 1465 2767 5449 11062 22585 45351 88361 166080 300819 525817 889147 1458580 2327551 3622381
+-6 -4 -1 10 42 112 239 440 722 1068 1415 1622 1426 384 -2201 -7372 -16630 -32068 -56529 -93790 -148774
+5283241$5