diff --git a/.gitattributes b/.gitattributes
index f8ecd08..95e9769 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1 @@
-input/day*.txt filter=git-crypt diff=git-crypt
+**/input/day*.txt filter=git-crypt diff=git-crypt
diff --git a/.gitignore b/.gitignore
index dc83987..1f9e66e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
-target/
+**/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
diff --git a/2016/Cargo.toml b/2016/Cargo.toml
new file mode 100644
index 0000000..b41f499
--- /dev/null
+++ b/2016/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "advent_of_code_2016"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+md5 = "0.7.0"
+rayon = "1.5.1"
diff --git a/2016/LICENSE b/2016/LICENSE
new file mode 100644
index 0000000..a406315
--- /dev/null
+++ b/2016/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 bOli
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/2016/README.md b/2016/README.md
new file mode 100644
index 0000000..650277c
--- /dev/null
+++ b/2016/README.md
@@ -0,0 +1,2 @@
+# Advent of Code
+My solutions to the [Advent of Code 2016](https://adventofcode.com/2016) puzzles.
diff --git a/2016/advent_of_code_2016.iml b/2016/advent_of_code_2016.iml
new file mode 100644
index 0000000..2fecef3
--- /dev/null
+++ b/2016/advent_of_code_2016.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2016/src/assembunny.rs b/2016/src/assembunny.rs
new file mode 100644
index 0000000..af98db8
--- /dev/null
+++ b/2016/src/assembunny.rs
@@ -0,0 +1,155 @@
+use std::collections::HashSet;
+use Op::*;
+use Param::*;
+
+type Register = char;
+type Value = isize;
+
+#[derive(Debug, Copy, Clone)]
+pub(crate) enum Param {
+ Register(Register),
+ Value(Value),
+}
+impl From<&str> for Param {
+ fn from(s: &str) -> Self {
+ if let Ok(number) = s.parse() {
+ Value(number)
+ } else {
+ Register(s.to_char())
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum Op {
+ Cpy(Param, Register),
+ Inc(Register),
+ Dec(Register),
+ Jnz(Param, Param),
+ // The two ops below are for day 23 only. This is the toggle command
+ Tgl(Register), // toggle
+ Nop(Param, Param), // no-op used to store the previous ops parameters
+ // The op below is for day 25 only
+ Out(Param), // Transmit the next clock signal value
+}
+impl From<&str> for Op {
+ fn from(s: &str) -> Self {
+ let p: Vec<_> = s.split_ascii_whitespace().collect();
+ match p[0] {
+ "cpy" => Cpy(Param::from(p[1]), p[2].to_char()),
+ "inc" => Inc(p[1].to_char()),
+ "dec" => Dec(p[1].to_char()),
+ "jnz" => Jnz(Param::from(p[1]), Param::from(p[2])),
+ "tgl" => Tgl(p[1].to_char()),
+ "out" => Out(Param::from(p[1])),
+ _ => panic!("Invalid op {}", s),
+ }
+ }
+}
+
+trait ToChar {
+ fn to_char(&self) -> char;
+}
+impl ToChar for &str {
+ fn to_char(&self) -> char {
+ self.chars().next().unwrap()
+ }
+}
+
+trait ToIndex {
+ fn to_idx(&self) -> usize;
+}
+impl ToIndex for char {
+ fn to_idx(&self) -> usize {
+ (*self as u8 - b'a') as usize
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Computer {
+ code: Vec,
+ register: Vec,
+}
+impl From> for Computer {
+ fn from(s: Vec<&str>) -> Self {
+ let code = s.into_iter().map(Op::from).collect();
+ let register = vec![0; 4];
+ Computer { code, register }
+ }
+}
+
+impl Computer {
+ pub(crate) fn run(&mut self) -> isize {
+ let mut instr_ptr = 0;
+ let mut prev_output = None;
+ let mut visited_states = HashSet::new();
+ while let Some(op) = self.code.get(instr_ptr) {
+ match op {
+ Cpy(i, r) => self.register[r.to_idx()] = self.get_value(i),
+ Inc(r) => self.register[r.to_idx()] += 1,
+ Dec(r) => self.register[r.to_idx()] -= 1,
+ Jnz(i, p) => {
+ if 0 != self.get_value(i) {
+ let offset = self.get_value(p);
+ let ip = instr_ptr as isize + offset;
+ if ip < 0 {
+ // still out of bounds, but valid for a usize
+ instr_ptr = self.code.len();
+ } else {
+ instr_ptr = ip as usize;
+ }
+ continue; // Avoid increasing of instr_ptr below
+ }
+ }
+ // This is for day 23 only
+ Tgl(r) => {
+ let offset = self.register[r.to_idx()];
+ let ip = instr_ptr as isize + offset;
+ if 0 <= ip && (ip as usize) < self.code.len() {
+ let op = self.code.get_mut(ip as usize).unwrap();
+ // println!("old op = {:?}", op);
+ match op {
+ Jnz(i, v) => match v {
+ Register(r) => *op = Cpy(*i, *r),
+ Value(_) => *op = Nop(*i, *v),
+ },
+ Cpy(i, r) => *op = Jnz(*i, Param::Register(*r)),
+ Nop(i, v) => *op = Jnz(*i, *v),
+ Inc(r) => *op = Dec(*r),
+ Dec(r) | Tgl(r) => *op = Inc(*r),
+ // Day 25 "Out" does not need to be handled for the day 23-only "Tgl"
+ Out(_) => {}
+ }
+ // println!("new op = {:?}", op);
+ } // else nothing happens if out of bounds
+ }
+ Nop(_, _) => {} // Just skip this no-op
+ Out(p) => {
+ let curr_output = self.get_value(p);
+ match (curr_output, prev_output) {
+ (0, None) | (0, Some(1)) | (1, Some(0)) => prev_output = Some(curr_output),
+ // Not a sequence of 0, 1, 0, 1, 0, 1, …
+ (_, _) => return -1, // denotes error
+ }
+ // Copy the computer's registers into a set to see if we're in an infinite loop,
+ // and stop if we are
+ if !visited_states.insert(format!("{:?}", self.register)) {
+ return 1; // denotes success
+ }
+ }
+ }
+ instr_ptr += 1;
+ }
+ self.register['a'.to_idx()]
+ }
+
+ fn get_value(&self, p: &Param) -> Value {
+ match p {
+ Param::Register(r) => self.register[r.to_idx()],
+ Param::Value(v) => *v,
+ }
+ }
+ pub(crate) fn set_register(&mut self, r: Register, v: Value) {
+ self.register[r.to_idx()] = v;
+ }
+}
diff --git a/2016/src/day01.rs b/2016/src/day01.rs
new file mode 100644
index 0000000..9b27d37
--- /dev/null
+++ b/2016/src/day01.rs
@@ -0,0 +1,123 @@
+use crate::parse;
+use std::collections::HashSet;
+
+const INPUT: &str = include_str!("../input/day01.txt");
+
+pub(crate) fn day01_part1() -> usize {
+ distance_from_origin(&parse(INPUT)[0])
+}
+
+pub(crate) fn day01_part2() -> usize {
+ distance_to_first_location_visited_twice(&parse(INPUT)[0])
+}
+
+fn distance_from_origin(input: &str) -> usize {
+ let (mut x, mut y) = (0isize, 0isize);
+ let mut dir = Dir::N;
+ for Step { turn, distance } in input.split(", ").map(Step::from).collect::>() {
+ dir.turn(&turn);
+ match dir {
+ Dir::N => y -= distance,
+ Dir::S => y += distance,
+ Dir::E => x += distance,
+ Dir::W => x -= distance,
+ }
+ }
+
+ (x.abs() + y.abs()) as usize
+}
+
+fn distance_to_first_location_visited_twice(input: &str) -> usize {
+ let (mut x, mut y) = (0isize, 0isize);
+ let mut dir = Dir::N;
+ let mut visited = HashSet::new();
+ visited.insert((x, y));
+ 'outer: for Step { turn, distance } in input.split(", ").map(Step::from).collect::>() {
+ dir.turn(&turn);
+ for _ in 0..distance {
+ match dir {
+ Dir::N => y -= 1,
+ Dir::S => y += 1,
+ Dir::E => x += 1,
+ Dir::W => x -= 1,
+ }
+ if !visited.insert((x, y)) {
+ break 'outer;
+ }
+ }
+ }
+
+ (x.abs() + y.abs()) as usize
+}
+
+enum Turn {
+ L,
+ R,
+}
+
+struct Step {
+ turn: Turn,
+ distance: isize,
+}
+impl From<&str> for Step {
+ fn from(s: &str) -> Self {
+ let distance = s[1..].parse().unwrap();
+ let turn = if s.starts_with('R') { Turn::R } else { Turn::L };
+ Step { turn, distance }
+ }
+}
+
+enum Dir {
+ N,
+ E,
+ S,
+ W,
+}
+impl Dir {
+ fn turn(&mut self, turn: &Turn) {
+ *self = match turn {
+ Turn::L => match self {
+ Dir::N => Dir::W,
+ Dir::E => Dir::N,
+ Dir::S => Dir::E,
+ Dir::W => Dir::S,
+ },
+ Turn::R => match self {
+ Dir::N => Dir::E,
+ Dir::E => Dir::S,
+ Dir::S => Dir::W,
+ Dir::W => Dir::N,
+ },
+ };
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn part1_examples() {
+ assert_eq!(5, distance_from_origin("R2, L3"));
+ assert_eq!(2, distance_from_origin("R2, R2, R2"));
+ assert_eq!(12, distance_from_origin("R5, L5, R5, R3"));
+ }
+
+ #[test]
+ fn part1() {
+ assert_eq!(230, day01_part1());
+ }
+
+ #[test]
+ fn part2_examples() {
+ assert_eq!(
+ 4,
+ distance_to_first_location_visited_twice("R8, R4, R4, R8")
+ );
+ }
+
+ #[test]
+ fn part2() {
+ assert_eq!(154, day01_part2());
+ }
+}
diff --git a/2016/src/day02.rs b/2016/src/day02.rs
new file mode 100644
index 0000000..8779d3e
--- /dev/null
+++ b/2016/src/day02.rs
@@ -0,0 +1,219 @@
+use crate::parse;
+use NumPad::*;
+
+const INPUT: &str = include_str!("../input/day02.txt");
+
+pub(crate) fn day02_part1() -> String {
+ bathroom_code(parse(INPUT), NumPadType::Simple)
+}
+
+pub(crate) fn day02_part2() -> String {
+ bathroom_code(parse(INPUT), NumPadType::Complex)
+}
+
+enum NumPadType {
+ Simple,
+ Complex,
+}
+fn bathroom_code(input: Vec<&str>, numpad_type: NumPadType) -> String {
+ let dirs: Vec> = input
+ .iter()
+ .map(|line| line.chars().map(Dir::from).collect())
+ .collect();
+ let mut numpad = NumPad::default();
+ let mut code: Vec = vec![];
+ for pattern in dirs {
+ match numpad_type {
+ NumPadType::Simple => numpad.apply_simple_pattern(pattern),
+ NumPadType::Complex => numpad.apply_complex_pattern(pattern),
+ }
+ code.push(numpad.to_char());
+ }
+
+ code.iter().collect()
+}
+
+enum Dir {
+ Up,
+ Down,
+ Left,
+ Right,
+}
+impl From for Dir {
+ fn from(c: char) -> Self {
+ match c {
+ 'U' => Dir::Up,
+ 'D' => Dir::Down,
+ 'L' => Dir::Left,
+ 'R' => Dir::Right,
+ _ => panic!("Unknown dir '{}'", c),
+ }
+ }
+}
+enum NumPad {
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+ Nine,
+ A,
+ B,
+ C,
+ D,
+}
+impl Default for NumPad {
+ fn default() -> Self {
+ Five
+ }
+}
+impl NumPad {
+ fn apply_simple_pattern(&mut self, pattern: Vec) {
+ for dir in pattern {
+ match dir {
+ Dir::Up => match self {
+ One | Two | Three => {}
+ Four => *self = One,
+ Five => *self = Two,
+ Six => *self = Three,
+ Seven => *self = Four,
+ Eight => *self = Five,
+ Nine => *self = Six,
+ _ => unreachable!(),
+ },
+ Dir::Down => match self {
+ One => *self = Four,
+ Two => *self = Five,
+ Three => *self = Six,
+ Four => *self = Seven,
+ Five => *self = Eight,
+ Six => *self = Nine,
+ Seven | Eight | Nine => {}
+ _ => unreachable!(),
+ },
+ Dir::Left => match self {
+ One | Four | Seven => {}
+ Two => *self = One,
+ Five => *self = Four,
+ Eight => *self = Seven,
+ Three => *self = Two,
+ Six => *self = Five,
+ Nine => *self = Eight,
+ _ => unreachable!(),
+ },
+ Dir::Right => match self {
+ One => *self = Two,
+ Four => *self = Five,
+ Seven => *self = Eight,
+ Two => *self = Three,
+ Five => *self = Six,
+ Eight => *self = Nine,
+ Three | Six | Nine => {}
+ _ => unreachable!(),
+ },
+ }
+ }
+ }
+ fn apply_complex_pattern(&mut self, pattern: Vec) {
+ for dir in pattern {
+ match dir {
+ Dir::Up => match self {
+ One | Two | Four | Five | Nine => {}
+ Three => *self = One,
+ Six => *self = Two,
+ Seven => *self = Three,
+ Eight => *self = Four,
+ A => *self = Six,
+ B => *self = Seven,
+ C => *self = Eight,
+ D => *self = B,
+ },
+ Dir::Down => match self {
+ One => *self = Three,
+ Two => *self = Six,
+ Three => *self = Seven,
+ Four => *self = Eight,
+ Six => *self = A,
+ Seven => *self = B,
+ Eight => *self = C,
+ B => *self = D,
+ Five | Nine | A | C | D => {}
+ },
+ Dir::Left => match self {
+ One | Two | Five | A | D => {}
+ Three => *self = Two,
+ Seven => *self = Six,
+ B => *self = A,
+ Four => *self = Three,
+ Eight => *self = Seven,
+ C => *self = B,
+ Six => *self = Five,
+ Nine => *self = Eight,
+ },
+ Dir::Right => match self {
+ Two => *self = Three,
+ Six => *self = Seven,
+ A => *self = B,
+ Three => *self = Four,
+ Seven => *self = Eight,
+ B => *self = C,
+ Five => *self = Six,
+ Eight => *self = Nine,
+ One | Four | Nine | C | D => {}
+ },
+ }
+ }
+ }
+ fn to_char(&self) -> char {
+ match self {
+ One => '1',
+ Two => '2',
+ Three => '3',
+ Four => '4',
+ Five => '5',
+ Six => '6',
+ Seven => '7',
+ Eight => '8',
+ Nine => '9',
+ A => 'A',
+ B => 'B',
+ C => 'C',
+ D => 'D',
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::parse;
+
+ const EXAMPLE: &str = "\
+ULL
+RRDDD
+LURDL
+UUUUD";
+
+ #[test]
+ fn part1_example() {
+ assert_eq!("1985", bathroom_code(parse(EXAMPLE), NumPadType::Simple));
+ }
+
+ #[test]
+ fn part1() {
+ assert_eq!("99332", day02_part1());
+ }
+
+ #[test]
+ fn part2_example() {
+ assert_eq!("5DB3", bathroom_code(parse(EXAMPLE), NumPadType::Complex));
+ }
+
+ #[test]
+ fn part2() {
+ assert_eq!("DD483", day02_part2());
+ }
+}
diff --git a/2016/src/day03.rs b/2016/src/day03.rs
new file mode 100644
index 0000000..11dbda2
--- /dev/null
+++ b/2016/src/day03.rs
@@ -0,0 +1,80 @@
+use crate::parse;
+
+const INPUT: &str = include_str!("../input/day03.txt");
+
+pub(crate) fn day03_part1() -> usize {
+ count_possible_triangle_rows(parse(INPUT))
+}
+
+pub(crate) fn day03_part2() -> usize {
+ count_possible_triangle_columns(parse(INPUT))
+}
+
+fn count_possible_triangle_rows(input: Vec<&str>) -> usize {
+ input
+ .iter()
+ .map(|line| {
+ line.split_ascii_whitespace()
+ .map(|n| n.parse().unwrap())
+ .collect::>()
+ })
+ .filter(|s| is_triangle(s[0], s[1], s[2]))
+ .count()
+}
+
+fn is_triangle(a: usize, b: usize, c: usize) -> bool {
+ let mut sides = [a, b, c];
+ sides.sort_unstable();
+ sides[0] + sides[1] > sides[2]
+}
+
+fn count_possible_triangle_columns(input: Vec<&str>) -> usize {
+ let input: Vec> = input
+ .iter()
+ .map(|line| {
+ line.split_ascii_whitespace()
+ .map(|n| n.parse().unwrap())
+ .collect()
+ })
+ .collect();
+ input
+ .windows(3)
+ .step_by(3)
+ .map(|n| {
+ // println!("{:?}", n);
+ (0..3)
+ .into_iter()
+ .filter(move |col| is_triangle(n[0][*col], n[1][*col], n[2][*col]))
+ .count()
+ })
+ .sum()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::parse;
+
+ #[test]
+ fn part1() {
+ assert_eq!(1050, day03_part1());
+ }
+
+ const EXAMPLE: &str = "\
+101 301 501
+102 302 502
+103 303 503
+201 401 601
+202 402 602
+203 403 603";
+
+ #[test]
+ fn part2_example() {
+ assert_eq!(6, count_possible_triangle_columns(parse(EXAMPLE)));
+ }
+
+ #[test]
+ fn part2() {
+ assert_eq!(1921, day03_part2());
+ }
+}
diff --git a/2016/src/day04.rs b/2016/src/day04.rs
new file mode 100644
index 0000000..a8ab459
--- /dev/null
+++ b/2016/src/day04.rs
@@ -0,0 +1,119 @@
+use crate::parse;
+use std::cmp::Ordering;
+use std::collections::HashMap;
+
+const INPUT: &str = include_str!("../input/day04.txt");
+
+pub(crate) fn day04_part1() -> usize {
+ sum_of_valid_sector_ids(parse(INPUT))
+}
+
+pub(crate) fn day04_part2() -> usize {
+ parse(INPUT)
+ .into_iter()
+ .filter_map(extract_valid_room)
+ .filter(|(name, id)| decrypt(name, id) == "northpole object storage")
+ .map(|(_, id)| id)
+ .next()
+ .unwrap()
+}
+
+fn sum_of_valid_sector_ids(input: Vec<&str>) -> usize {
+ input
+ .into_iter()
+ .filter_map(extract_valid_room)
+ .map(|(_, id)| id)
+ .sum()
+}
+
+fn extract_valid_room>(name: T) -> Option<(String, usize)> {
+ let (enc_name, id_checksum) = name.as_ref().rsplit_once('-').unwrap();
+ let (sector_id, actual_checksum) = id_checksum.trim_end_matches(']').split_once('[').unwrap();
+ if actual_checksum == expected_checksum(enc_name) {
+ Some((enc_name.to_string(), sector_id.parse().unwrap()))
+ } else {
+ None
+ }
+}
+
+fn decrypt>(name: T, id: &usize) -> String {
+ let mut name = name.as_ref().replace("-", " ").chars().collect::>();
+ let shift = (id % 26) as u8;
+ name.iter_mut().for_each(|c| {
+ let mut i = *c as u8;
+ if (b'a'..=b'z').contains(&i) {
+ i += shift;
+ if i > b'z' {
+ i -= 26;
+ }
+ *c = i as char
+ }
+ });
+ name.iter().collect()
+}
+
+fn expected_checksum(name: &str) -> String {
+ let mut frequencies = HashMap::new();
+ let letters = name.replace("-", "");
+ letters.chars().into_iter().for_each(|c| {
+ *frequencies.entry(c).or_insert(0usize) += 1;
+ });
+ let mut frequencies: Vec<_> = frequencies.into_iter().collect();
+ frequencies.sort_unstable_by(|(char_a, count_a), (char_b, count_b)| {
+ match count_a.cmp(count_b).reverse() {
+ Ordering::Equal => char_a.cmp(char_b),
+ by_count => by_count,
+ }
+ });
+ let expected_checksum: String = frequencies.into_iter().take(5).map(|(c, _)| c).collect();
+ expected_checksum
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::parse;
+
+ const EXAMPLE: &str = "\
+aaaaa-bbb-z-y-x-123[abxyz]
+a-b-c-d-e-f-g-h-987[abcde]
+not-a-real-room-404[oarel]
+totally-real-room-200[decoy]";
+
+ #[test]
+ fn test_extract_valid_room() {
+ assert_eq!(
+ Some(("aaaaa-bbb-z-y-x".to_string(), 123)),
+ extract_valid_room("aaaaa-bbb-z-y-x-123[abxyz]")
+ );
+ assert_eq!(
+ Some(("a-b-c-d-e-f-g-h".to_string(), 987)),
+ extract_valid_room("a-b-c-d-e-f-g-h-987[abcde]")
+ );
+ assert_eq!(
+ Some(("not-a-real-room".to_string(), 404)),
+ extract_valid_room("not-a-real-room-404[oarel]")
+ );
+ assert_eq!(None, extract_valid_room("totally-real-room-200[decoy]"));
+ }
+
+ #[test]
+ fn part1_example() {
+ assert_eq!(1514, sum_of_valid_sector_ids(parse(EXAMPLE)));
+ }
+
+ #[test]
+ fn part1() {
+ assert_eq!(158835, day04_part1());
+ }
+
+ #[test]
+ fn test_get_real_name() {
+ assert_eq!("very encrypted name", decrypt("qzmt-zixmtkozy-ivhz", &343));
+ }
+
+ #[test]
+ fn part2() {
+ assert_eq!(993, day04_part2());
+ }
+}
diff --git a/2016/src/day05.rs b/2016/src/day05.rs
new file mode 100644
index 0000000..082e5b2
--- /dev/null
+++ b/2016/src/day05.rs
@@ -0,0 +1,179 @@
+use md5::Digest;
+use rayon::prelude::*;
+use std::thread;
+
+const PUZZLE_INPUT: &str = "abbhdwsy";
+
+pub(crate) fn day05_part1() -> String {
+ generate_part1_password_from(PUZZLE_INPUT)
+}
+
+pub(crate) fn day05_part2() -> String {
+ generate_part2_password_from(PUZZLE_INPUT)
+}
+
+// The following code is adapted from 2015 day 4
+
+#[allow(unused)]
+enum CalcType {
+ Threaded, // About 10s for part 1 | 4s part 2 example | 9s part 2
+ Parallel, // About 3s for part 1 | 5s part 2 example | 9s part 2
+ Single, // About 23s for part 1 | 39s part 2 example | 74s part 2
+}
+
+fn generate_part1_password_from(secret_key: &str) -> String {
+ generate_password_with_filter(secret_key, Part::One)
+}
+fn generate_part2_password_from(secret_key: &str) -> String {
+ generate_password_with_filter(secret_key, Part::Two)
+}
+
+#[derive(PartialEq)]
+enum Part {
+ One,
+ Two,
+}
+
+fn generate_password_with_filter(secret_key: &str, part: Part) -> String {
+ let calc = CalcType::Parallel;
+ let mut results: Vec<(usize, char, char)> = Vec::new();
+ let mut password: Vec