Browse Source

Add day 7 and 9-13

niels 2 years ago
parent
commit
6699449ef4
15 changed files with 963 additions and 0 deletions
  1. 24 0
      Cargo.lock
  2. 2 0
      Cargo.toml
  3. 86 0
      src/bin/day10.rs
  4. 175 0
      src/bin/day11.rs
  5. 119 0
      src/bin/day12.rs
  6. 161 0
      src/bin/day13.rs
  7. 62 0
      src/bin/day7.rs
  8. 87 0
      src/bin/day9.rs
  9. 147 0
      test_inputs/day10.txt
  10. 28 0
      test_inputs/day11.txt
  11. 6 0
      test_inputs/day12.txt
  12. 24 0
      test_inputs/day13.txt
  13. 24 0
      test_inputs/day7.txt
  14. 9 0
      test_inputs/day9-1.txt
  15. 9 0
      test_inputs/day9.txt

+ 24 - 0
Cargo.lock

@@ -39,7 +39,9 @@ dependencies = [
  "curl",
  "hex",
  "itertools",
+ "nom",
  "petgraph",
+ "primes",
  "regex",
  "tempfile",
 ]
@@ -330,6 +332,22 @@ version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.45"
@@ -396,6 +414,12 @@ version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
 
+[[package]]
+name = "primes"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68a61082d8bceecd71a3870e9162002bb75f7ba9c7aa8b76227e887782fef9c8"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.47"

+ 2 - 0
Cargo.toml

@@ -17,3 +17,5 @@ curl = "0.4.34"
 tempfile = "3.1.0"
 itertools = "0.10.1"
 petgraph = "0.6.0"
+primes = "0.3.0"
+nom = "7.1.1"

+ 86 - 0
src/bin/day10.rs

@@ -0,0 +1,86 @@
+use std::{str::FromStr, num::ParseIntError};
+
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day10";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+enum Instr {
+    NoOp, AddX(i64)
+}
+
+impl Instr {
+    fn run(&self, reg: u64) -> u64 {
+        match self {
+            Instr::NoOp => reg,
+            Instr::AddX(v) => (reg as i64 + v) as u64,
+        }
+    }
+
+    fn len(&self) -> u64 {
+        match self {
+            Instr::NoOp => 1,
+            Instr::AddX(_) => 2,
+        }
+    }
+}
+
+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")
+        }
+    }
+}
+
+
+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);
+        }
+        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;
+            }
+        }
+        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!("{}", print_str);
+    Ok([Some(strength.to_string()), Some(test_str)])
+}
+
+#[test]
+pub fn test_day10() {
+    assert!(common::run_test(DAY, &run))
+}

+ 175 - 0
src/bin/day11.rs

@@ -0,0 +1,175 @@
+use std::fmt::Display;
+
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day11";
+
+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,
+        }
+    }
+}
+
+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),
+        }
+    }
+}
+
+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());
+        }
+    }
+    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;
+        }
+    }
+    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;
+        }
+    }
+    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)])
+}
+
+#[test]
+pub fn test_day11() {
+    assert!(common::run_test(DAY, &run))
+}

+ 119 - 0
src/bin/day12.rs

@@ -0,0 +1,119 @@
+use std::{
+    cmp::Reverse,
+    collections::{BinaryHeap, HashMap, HashSet},
+};
+
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day12";
+
+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)),
+        }
+    }
+}
+
+const DIRECTIONS: [Dirs; 4] = [Dirs::Left, Dirs::Up, Dirs::Right, Dirs::Down];
+
+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 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);
+                    }
+                }
+            }
+        }
+    }
+
+    let length = length.ok_or("No path to goal found")?;
+
+    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);
+        }
+    }
+
+    Ok([Some(length.to_string()), Some(shortest_path.to_string())])
+}
+
+#[test]
+pub fn test_day12() {
+    assert!(common::run_test(DAY, &run))
+}

+ 161 - 0
src/bin/day13.rs

@@ -0,0 +1,161 @@
+use std::{cmp::Ordering, ops::Index};
+
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+use itertools::Itertools;
+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,
+};
+
+const DAY: &str = "day13";
+
+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
+            }
+            (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
+                } else {
+                    SymbolsOrdering::Equal
+                }
+            }
+        }
+    }
+}
+
+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)]);
+                }
+                SymbolsOrdering::RaiseRightNum(n) => {
+                    front_log_right.extend([Symbols::RBracket, Symbols::Num(n)]);
+                }
+            };
+        }
+        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;
+        }
+    }
+}
+
+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;
+                }
+                None => panic!("Found indentical inputs: {:?}", pair),
+                _ => (),
+            }
+
+            packets.push(left);
+            packets.push(right);
+        }
+    }
+
+    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())])
+}
+
+#[test]
+pub fn test_day13() {
+    assert!(common::run_test(DAY, &run))
+}

+ 62 - 0
src/bin/day7.rs

@@ -0,0 +1,62 @@
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day7";
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    common::run(DAY, &run)
+}
+
+
+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>()?;
+        }
+    }
+    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;
+        }
+    }
+
+    let excess = 30000000 - (70000000 - root_size);
+
+    let mut min = u64::MAX;
+    for dir in dir_sizes {
+        if dir > excess {
+            min = min.min(dir);
+        }
+    }
+
+    Ok([Some(small_sum.to_string()), Some(min.to_string())])
+}
+
+#[test]
+pub fn test_day7() {
+    assert!(common::run_test(DAY, &run))
+}

+ 87 - 0
src/bin/day9.rs

@@ -0,0 +1,87 @@
+use std::collections::HashSet;
+
+use aoc2022_niels_overkamp::common::{self, AOCResult};
+
+const DAY: &str = "day9";
+
+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;
+            }
+            // println!("R: {:?}", rope);
+        }
+    }
+
+    Ok([Some(visited2.len().to_string()), Some(visited10.len().to_string())])
+}
+
+#[test]
+pub fn test_day9() {
+    assert!(common::run_test(DAY, &run))
+}

+ 147 - 0
test_inputs/day10.txt

@@ -0,0 +1,147 @@
+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$##..##..##..##..##..##..##..##..##..##..###...###...###...###...###...###...###.####....####....####....####....####....#####.....#####.....#####.....#####.....######......######......######......###########.......#######.......#######.....

+ 28 - 0
test_inputs/day11.txt

@@ -0,0 +1,28 @@
+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

+ 6 - 0
test_inputs/day12.txt

@@ -0,0 +1,6 @@
+Sabqponm
+abcryxxl
+accszExk
+acctuvwj
+abdefghi
+31$29

+ 24 - 0
test_inputs/day13.txt

@@ -0,0 +1,24 @@
+[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

+ 24 - 0
test_inputs/day7.txt

@@ -0,0 +1,24 @@
+$ 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

+ 9 - 0
test_inputs/day9-1.txt

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

+ 9 - 0
test_inputs/day9.txt

@@ -0,0 +1,9 @@
+R 4
+U 4
+L 3
+D 1
+R 4
+D 1
+L 5
+R 2
+13$1