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> = vec![None; 8]; + match calc { + CalcType::Threaded => { + let thread_count = 16; + let step_size = 10_000; // seems to work fine on my 9900K (8-cores, 16-threads) + let mut start = 0; + while (part == Part::One && results.len() < 8) + || (part == Part::Two && password.iter().any(|x| x.is_none())) + { + let mut handles = vec![]; + for from in start..(start + thread_count) { + let secret_key = secret_key.to_string(); + handles.push(thread::spawn(move || { + let mut results = vec![]; + for i in (from..from + step_size).into_iter().step_by(thread_count) { + if let Some(result) = check_match(&secret_key, i) { + results.push(result); + } + } + Some(results) + })); + } + for handle in handles { + if let Some(mut result) = handle.join().unwrap() { + results.append(&mut result); + } + } + results.sort_by_key(|&(i, _, _)| i); + if part == Part::Two { + results.iter().for_each(|(_, sixth, seventh)| { + let pos = sixth.to_digit(16).unwrap() as usize; + if pos < 8 && password[pos].is_none() { + password[pos] = Some(*seventh); + } + }); + } + start += step_size; + } + } + CalcType::Parallel => { + let step_size = 1_000_000; // seems to work fine on my 9900K (8-cores, 16-threads) + let mut start = 0; + while (part == Part::One && results.len() < 8) + || (part == Part::Two && password.iter().any(|x| x.is_none())) + { + let mut matches: Vec<_> = (start..(start + step_size)) + .into_par_iter() + .filter_map(|i| check_match(secret_key, i)) + .collect(); + matches.sort_by_key(|&(i, _, _)| i); + + results.append(&mut matches); + if part == Part::Two { + results.iter().for_each(|(_, sixth, seventh)| { + let pos = sixth.to_digit(16).unwrap() as usize; + if pos < 8 && password[pos].is_none() { + password[pos] = Some(*seventh); + } + }); + } + start += step_size; + } + } + CalcType::Single => match part { + Part::One => { + results = (0..(usize::MAX)) + .into_iter() + .filter_map(|i| check_match(secret_key, i)) + .take(8) + .collect(); + } + Part::Two => { + let mut i = 0; + while password.iter().any(|x| x.is_none()) { + if let Some((_, sixth, seventh)) = check_match(secret_key, i) { + let pos = sixth.to_digit(16).unwrap() as usize; + if pos < 8 && password[pos].is_none() { + password[pos] = Some(seventh); + } + } + i += 1; + } + } + }, + } + match part { + Part::One => results + .into_iter() + .take(8) + .map(|(_, c, _)| c) + .collect::(), + Part::Two => password.into_iter().map(|x| x.unwrap()).collect::(), + } +} + +fn check_match(secret_key: &str, i: usize) -> Option<(usize, char, char)> { + let digest = md5::compute(format!("{}{}", secret_key, i)); + if starts_with_5_leading_zeroes(digest) { + let hash = format!("{:x}", digest); + // println!("digest {}", hash); + Some(( + i, + hash.chars().nth(5).unwrap(), + hash.chars().nth(6).unwrap(), + )) + } else { + None + } +} + +fn starts_with_5_leading_zeroes(digest: Digest) -> bool { + // The 5 zeroes are made up of 5 hex digits + // Two hex digits are made up of a single u8 + // 16 equals 0b10000, so < 16 means the left-most 4 bits are zero + digest[0] == 0 && digest[1] == 0 && digest[2] < 16 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!("18f47a30", generate_part1_password_from("abc")); + } + #[test] + fn part1() { + assert_eq!("801b56a7", day05_part1()); + } + + #[test] + fn part2_example() { + assert_eq!("05ace8e3", generate_part2_password_from("abc")); + } + + #[test] + fn part2() { + assert_eq!("424a0197", day05_part2()); + } +} diff --git a/2016/src/day06.rs b/2016/src/day06.rs new file mode 100644 index 0000000..23e8e6a --- /dev/null +++ b/2016/src/day06.rs @@ -0,0 +1,85 @@ +use crate::parse; +use std::collections::HashMap; + +const INPUT: &str = include_str!("../input/day06.txt"); + +pub(crate) fn day06_part1() -> String { + error_corrected_message(parse(INPUT), Part::One) +} + +pub(crate) fn day06_part2() -> String { + error_corrected_message(parse(INPUT), Part::Two) +} + +enum Part { + One, + Two, +} +fn error_corrected_message(messages: Vec<&str>, part: Part) -> String { + let mut corrected = vec![]; + for idx in 0..messages[0].len() { + let mut frequency = HashMap::new(); + for message in &messages { + *frequency + .entry(message.chars().nth(idx).unwrap()) + .or_insert(0) += 1; + } + let ch = match part { + Part::One => frequency.iter().max_by_key(|(_ch, &freq)| freq), + Part::Two => frequency.iter().min_by_key(|(_ch, &freq)| freq), + }; + corrected.push(*ch.map(|(ch, _freq)| ch).unwrap()); + } + + corrected.iter().collect::() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +eedadn +drvtee +eandsr +raavrd +atevrs +tsrnev +sdttsa +rasrtv +nssdts +ntnada +svetve +tesnvt +vntsnd +vrdear +dvrsen +enarar"; + + #[test] + fn part1_example() { + assert_eq!( + "easter".to_string(), + error_corrected_message(parse(EXAMPLE), Part::One) + ); + } + + #[test] + fn part1() { + assert_eq!("qtbjqiuq", day06_part1()); + } + + #[test] + fn part2_example() { + assert_eq!( + "advent".to_string(), + error_corrected_message(parse(EXAMPLE), Part::Two) + ); + } + + #[test] + fn part2() { + assert_eq!("akothqli", day06_part2()); + } +} diff --git a/2016/src/day07.rs b/2016/src/day07.rs new file mode 100644 index 0000000..8bc80d6 --- /dev/null +++ b/2016/src/day07.rs @@ -0,0 +1,93 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day07.txt"); + +pub(crate) fn day07_part1() -> usize { + parse(INPUT).into_iter().filter(|s| supports_tls(s)).count() +} + +pub(crate) fn day07_part2() -> usize { + parse(INPUT).into_iter().filter(|s| supports_ssl(s)).count() +} + +fn supports_tls(ip: &str) -> bool { + let (must_have_an_abba, must_not_have_any_abbas) = split(ip); + + must_have_an_abba.iter().any(|s| has_abba(s)) + && must_not_have_any_abbas.iter().all(|s| !has_abba(s)) +} + +fn supports_ssl(ip: &str) -> bool { + let (supernet, hypernet) = split(ip); + + let babs: Vec = supernet + .into_iter() + .flat_map(|line| { + line.chars() + .collect::>() + .windows(3) + .filter(|w| w[0] == w[2] && w[1] != w[0]) + .map(|w| format!("{}{}{}", w[1], w[0], w[1])) + .collect::>() + }) + .collect(); + + hypernet.iter().any(|s| babs.iter().any(|b| s.contains(b))) +} + +fn split(ip: &str) -> (Vec<&str>, Vec<&str>) { + ( + ip.split(is_bracket).step_by(2).collect(), + ip.split(is_bracket).skip(1).step_by(2).collect(), + ) +} + +fn is_bracket(c: char) -> bool { + c == '[' || c == ']' +} + +fn has_abba(s: &str) -> bool { + s.chars() + .collect::>() + .windows(4) + .any(|w| w[0] != w[1] && w[2] == w[1] && w[3] == w[0]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_has_abba() { + assert!(has_abba("abba")); + assert!(!has_abba("abcd")); + assert!(!has_abba("qwer")); + assert!(has_abba("ioxxoj")); + } + + #[test] + fn part1_examples() { + assert!(supports_tls("abba[mnop]qrst")); + assert!(!supports_tls("abcd[bddb]xyyx")); + assert!(!supports_tls("aaaa[qwer]tyui")); + assert!(supports_tls("ioxxoj[asdfgh]zxcvbn")); + } + + #[test] + fn part1() { + assert_eq!(105, day07_part1()); + } + + #[test] + fn part2_examples() { + assert!(supports_ssl("aba[bab]xyz")); + assert!(!supports_ssl("xyx[xyx]xyx")); + assert!(supports_ssl("aaa[kek]eke")); + assert!(supports_ssl("zazbz[bzb]cdb")); + } + + #[test] + fn part2() { + assert_eq!(258, day07_part2()); + } +} diff --git a/2016/src/day08.rs b/2016/src/day08.rs new file mode 100644 index 0000000..af92029 --- /dev/null +++ b/2016/src/day08.rs @@ -0,0 +1,188 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day08.txt"); + +pub(crate) fn day08_part1() -> usize { + let screen = apply_all_input_operations_to_screen(); + screen.count_on_pixels() +} + +pub(crate) fn day08_part2() -> String { + let screen = apply_all_input_operations_to_screen(); + if screen.to_string() + == "\ +####...##.#..#.###..#..#..##..###..#....#...#..##. +...#....#.#..#.#..#.#.#..#..#.#..#.#....#...#...#. +..#.....#.####.#..#.##...#....#..#.#.....#.#....#. +.#......#.#..#.###..#.#..#....###..#......#.....#. +#....#..#.#..#.#.#..#.#..#..#.#....#......#..#..#. +####..##..#..#.#..#.#..#..##..#....####...#...##.." + { + "ZJHRKCPLYJ" + } else { + "fail" + } + .to_string() +} + +fn apply_all_input_operations_to_screen() -> Screen { + let mut screen = Screen::new(50, 6); + for op in parse_operations() { + screen.apply(op); + } + screen +} + +fn parse_operations() -> Vec { + parse(INPUT).into_iter().map(Op::from).collect() +} + +#[derive(PartialEq, Debug)] +enum Op { + Rect(usize, usize), + RotateRow(usize, usize), + RotateColumn(usize, usize), +} +impl> From for Op { + fn from(s: T) -> Self { + let parts: Vec<_> = s + .as_ref() + .split(|c| c == ' ' || c == 'x' || c == '=') + .collect(); + // println!("parts = {:?}", parts); + if parts[0] == "rect" { + Op::Rect(parts[1].parse().unwrap(), parts[2].parse().unwrap()) + } else if parts[1] == "column" { + Op::RotateColumn(parts[4].parse().unwrap(), parts[6].parse().unwrap()) + } else if parts[1] == "row" { + Op::RotateRow(parts[3].parse().unwrap(), parts[5].parse().unwrap()) + } else { + panic!("Invalid input '{}'", s.as_ref()) + } + } +} + +struct Screen { + grid: Vec>, +} + +impl Screen { + fn new(width: usize, height: usize) -> Self { + Screen { + grid: vec![vec![false; width]; height], + } + } + fn apply(&mut self, op: Op) { + match op { + Op::Rect(w, h) => { + self.grid + .iter_mut() + .take(h) + .for_each(|row| row.iter_mut().take(w).for_each(|pixel| *pixel = true)); + } + Op::RotateRow(y, o) => { + self.grid[y].rotate_right(o); + } + Op::RotateColumn(x, o) => { + let mut col: Vec<_> = self.grid.iter().map(|row| row[x]).collect(); + col.rotate_right(o); + for (y, row) in self.grid.iter_mut().enumerate() { + row[x] = col[y]; + } + } + } + } + fn count_on_pixels(&self) -> usize { + self.grid + .iter() + .map(|row| row.iter().filter(|&&on| on).count()) + .sum() + } +} +impl ToString for Screen { + fn to_string(&self) -> String { + self.grid + .iter() + .map(|row| { + row.iter() + .map(|&on| if on { '#' } else { '.' }) + .collect::() + }) + .collect::>() + .join("\n") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_ops() { + assert_eq!(Op::Rect(1, 2), Op::from("rect 1x2")); + assert_eq!(Op::RotateRow(1, 2), Op::from("rotate row y=1 by 2")); + assert_eq!(Op::RotateColumn(1, 2), Op::from("rotate column x=1 by 2")); + } + + #[test] + fn screen_to_string() { + let screen = Screen::new(7, 3); + assert_eq!( + "\ +....... +....... +.......", + screen.to_string() + ); + } + + #[test] + fn apply_operations_to_screen() { + let mut screen = Screen::new(7, 3); + screen.apply(Op::from("rect 3x2")); + assert_eq!( + "\ +###.... +###.... +.......", + screen.to_string() + ); + + screen.apply(Op::from("rotate column x=1 by 1")); + assert_eq!( + "\ +#.#.... +###.... +.#.....", + screen.to_string() + ); + + screen.apply(Op::from("rotate row y=0 by 4")); + assert_eq!( + "\ +....#.# +###.... +.#.....", + screen.to_string() + ); + + screen.apply(Op::from("rotate column x=1 by 1")); + assert_eq!( + "\ +.#..#.# +#.#.... +.#.....", + screen.to_string() + ); + } + + #[test] + fn part1() { + assert_eq!(110, day08_part1()); + } + + #[test] + fn part2() { + assert_eq!("ZJHRKCPLYJ", day08_part2()); + } +} diff --git a/2016/src/day09.rs b/2016/src/day09.rs new file mode 100644 index 0000000..a8cb240 --- /dev/null +++ b/2016/src/day09.rs @@ -0,0 +1,163 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day09.txt"); + +pub(crate) fn day09_part1() -> usize { + let input = &parse(INPUT)[0]; + outer_only_decompress(input).len() +} + +pub(crate) fn day09_part2() -> usize { + let input = &parse(INPUT)[0]; + full_decompressed_len(input) +} + +fn outer_only_decompress>(s: T) -> String { + part1_decompress(&to_char_vec(s)).iter().collect() +} +fn part1_decompress(s: &[char]) -> Vec { + let mut res: Vec = vec![]; + let mut i = 0; + while i < s.len() { + // println!("looking at {}", s[i]); + if s[i] == '(' { + let (marker_len, seq_len, seq_count) = parse_marker(s, i); + i += marker_len; + let seq = &s[i..i + seq_len]; + i += seq_len; + // println!("to_repeat = {:?}", seq); + res.append(&mut seq.repeat(seq_count)); + } else { + res.push(s[i]); + i += 1; + } + } + res +} + +fn full_decompressed_len>(s: T) -> usize { + part2_decompress_len(&to_char_vec(s)) +} +fn part2_decompress_len(s: &[char]) -> usize { + let mut len = 0; + let mut i = 0; + while i < s.len() { + if s[i] == '(' { + let (marker_len, seq_len, seq_count) = parse_marker(s, i); + i += marker_len; + let seq = &s[i..i + seq_len]; + i += seq_len; + len += seq_count * part2_decompress_len(seq); + } else { + i += 1; + len += 1; + } + } + len +} + +fn to_char_vec>(s: T) -> Vec { + s.as_ref().chars().collect() +} + +fn parse_marker(s: &[char], start: usize) -> (usize, usize, usize) { + let mut end = start; + while s[end] != ')' { + end += 1; + } + end += 1; // Include ')' + + let marker: String = s[start + 1..end - 1].iter().collect::(); + let marker_len = marker.len() + 2; // '(' ')' + let (seq_len, seq_count) = marker.split_once("x").unwrap(); + let seq_len: usize = seq_len.parse().unwrap(); + let seq_count: usize = seq_count.parse().unwrap(); + // println!( + // "marker ({}) of len {} -> seq_len {}, seq_count = {}", + // marker.to_string(), + // marker_len, + // seq_len, + // seq_count, + // ); + (marker_len, seq_len, seq_count) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_decompress_without_marker() { + assert_eq!("ADVENT", outer_only_decompress("ADVENT")); + } + + #[test] + fn part1_decompress_with_1x5_marker() { + assert_eq!("ABBBBBC", outer_only_decompress("A(1x5)BC")); + } + + #[test] + fn part1_decompress_with_3x3_marker() { + assert_eq!("XYZXYZXYZ", outer_only_decompress("(3x3)XYZ")); + } + + #[test] + fn part1_decompress_with_ignored_1x3_marker() { + assert_eq!("(1x3)A", outer_only_decompress("(6x1)(1x3)A")); + } + + #[test] + fn part1_decompress_with_ignored_3x3_marker() { + assert_eq!( + "X(3x3)ABC(3x3)ABCY", + outer_only_decompress("X(8x2)(3x3)ABCY") + ); + } + + #[test] + fn part1() { + assert_eq!(112_830, day09_part1()); + } + + fn decompress_repeatedly>(s: T) -> String { + let mut decompressed = part1_decompress(&to_char_vec(s)); + while decompressed.contains(&'(') { + decompressed = part1_decompress(&decompressed); + } + decompressed.iter().collect() + } + + #[test] + fn part2_decompress_with_3x3_marker() { + assert_eq!("XYZXYZXYZ", decompress_repeatedly("(3x3)XYZ")); + } + + #[test] + fn part2_decompress_with_8x2_and_3x3_markers() { + assert_eq!( + "XABCABCABCABCABCABCY", + decompress_repeatedly("X(8x2)(3x3)ABCY") + ); + } + + #[test] + fn part2_decompress_very_long() { + assert_eq!( + 241_920, + full_decompressed_len("(27x12)(20x12)(13x14)(7x10)(1x12)A") + ); + } + + #[test] + fn part2_example_4() { + assert_eq!( + 445, + full_decompressed_len("(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN") + ); + } + + #[test] + fn part2() { + assert_eq!(10_931_789_799, day09_part2()); + } +} diff --git a/2016/src/day10.rs b/2016/src/day10.rs new file mode 100644 index 0000000..fe02c41 --- /dev/null +++ b/2016/src/day10.rs @@ -0,0 +1,186 @@ +use crate::parse; +use std::collections::HashMap; + +const INPUT: &str = include_str!("../input/day10.txt"); + +pub(crate) fn day10_part1() -> usize { + let input = parse(INPUT); + let mut bot_traders = BotTraders::from(input); + bot_traders.bot_responsible_for_handling(61, 17) +} + +pub(crate) fn day10_part2() -> usize { + let input = parse(INPUT); + let mut bot_traders = BotTraders::from(input); + bot_traders.trade(&|_| false) +} + +type Number = usize; +type Bot = Number; +type Value = Number; +type Output = Number; + +struct BotTraders { + values_by_bot: HashMap>, + values_by_output: HashMap>, + recipients_by_bot: HashMap, +} +impl BotTraders { + fn bot_responsible_for_handling(&mut self, chip_a: Number, chip_b: Number) -> Bot { + let stop_filter = |values: &Vec| { + values[0] == chip_a && values[1] == chip_b || values[0] == chip_b && values[1] == chip_a + }; + self.trade(&stop_filter) + } + fn trade(&mut self, stop_filter: &dyn Fn(&Vec) -> bool) -> Bot { + while let Some((bot, values)) = self + .values_by_bot + .iter() + .find(|(bot, values)| values.len() == 2 && self.recipients_by_bot.contains_key(bot)) + { + let bot = *bot; + // println!("bot {} has values {:?}", bot, values); + if stop_filter(values) { + return bot; + } + let values = self.values_by_bot.remove(&bot).unwrap(); + + if let Some(recipients) = self.recipients_by_bot.get(&bot) { + let (lo, hi) = if values[0] < values[1] { + (values[0], values[1]) + } else { + (values[1], values[0]) + }; + match recipients.lo { + Recipient::Bot(b) => self.values_by_bot.entry(b), + Recipient::Output(o) => self.values_by_output.entry(o), + } + .or_insert_with(Vec::new) + .push(lo); + match recipients.hi { + Recipient::Bot(b) => self.values_by_bot.entry(b), + Recipient::Output(o) => self.values_by_output.entry(o), + } + .or_insert_with(Vec::new) + .push(hi); + } + } + self.values_by_output.get(&0).unwrap().first().unwrap() + * self.values_by_output.get(&1).unwrap().first().unwrap() + * self.values_by_output.get(&2).unwrap().first().unwrap() + } +} +impl From> for BotTraders { + fn from(input: Vec<&str>) -> Self { + let (initial_values, bots): (Vec<_>, Vec<_>) = + input.into_iter().partition(|l| l.starts_with("value")); + + let mut values_by_bot: HashMap> = HashMap::new(); + initial_values.into_iter().for_each(|s| { + // Example: value 2 goes to bot 171 + let s: Vec<_> = s.split_ascii_whitespace().collect(); + values_by_bot + .entry(s[5].parse().unwrap()) + .or_insert_with(Vec::new) + .push(s[1].parse().unwrap()); + }); + // println!("values {:?}", values); + + let recipients_by_bot: HashMap = bots + .into_iter() + .map(|s| { + // Example: bot 51 gives low to bot 19 and high to bot 176 + // Index: 0 1 2 3 4 5 6 7 8 9 10 11 + let s: Vec<_> = s.split_ascii_whitespace().collect(); + (s[1].parse::().unwrap(), Recipients::from(&s[3..=11])) + }) + .collect(); + // println!("bots {:?}", bots); + + BotTraders { + values_by_bot, + values_by_output: HashMap::new(), + recipients_by_bot, + } + } +} + +#[derive(Debug)] +struct Recipients { + lo: Recipient, + hi: Recipient, +} +impl From<&[&str]> for Recipients { + fn from(s: &[&str]) -> Self { + // Example: low to bot 14 and high to bot 111 + // Index: 0 1 2 3 4 5 6 7 8 + Recipients { + lo: Recipient::from(&s[0..=3]), + hi: Recipient::from(&s[5..=8]), + } + } +} + +#[derive(Debug)] +enum Recipient { + Bot(Bot), + Output(Value), +} +impl From<&[&str]> for Recipient { + fn from(s: &[&str]) -> Self { + // Examples: bot 0 gives low to output 2 + // high to output 0 + // Index: 0 1 2 3 + let number = s[3].parse().unwrap(); + match s[2] { + "bot" => Recipient::Bot(number), + "output" => Recipient::Output(number), + _ => panic!("Invalid Recipient '{:?}'", s), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +value 5 goes to bot 2 +bot 2 gives low to bot 1 and high to bot 0 +value 3 goes to bot 1 +bot 1 gives low to output 1 and high to bot 0 +bot 0 gives low to output 2 and high to output 0 +value 2 goes to bot 2"; + + #[test] + fn part1_example_initial() { + let input = parse(EXAMPLE); + let mut bot_traders = BotTraders::from(input); + assert_eq!(2, bot_traders.bot_responsible_for_handling(2, 5)); + } + + #[test] + fn part1_example_after_first_distribution() { + let input = parse(EXAMPLE); + let mut bot_traders = BotTraders::from(input); + assert_eq!(1, bot_traders.bot_responsible_for_handling(2, 3)); + } + + #[test] + fn part1_example_after_second_distribution() { + let input = parse(EXAMPLE); + let mut bot_traders = BotTraders::from(input); + assert_eq!(1, bot_traders.bot_responsible_for_handling(2, 3)); + } + + #[test] + fn part1() { + assert_eq!(86, day10_part1()); + } + + #[test] + fn part2() { + assert_eq!(67 * 11 * 31, day10_part2()); + } +} diff --git a/2016/src/day11.rs b/2016/src/day11.rs new file mode 100644 index 0000000..b17f689 --- /dev/null +++ b/2016/src/day11.rs @@ -0,0 +1,306 @@ +use crate::parse; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashSet}; + +const INPUT: &str = include_str!("../input/day11.txt"); + +pub(crate) fn day11_part1() -> usize { + let mut facility = Facility::from(parse(INPUT)); + facility.steps_to_bring_everything_to_floor_3() +} + +pub(crate) fn day11_part2() -> usize { + let mut facility = Facility::from(parse(INPUT)); + facility.microchips.push(0); + facility.generators.push(0); + facility.microchips.push(0); + facility.generators.push(0); + facility.steps_to_bring_everything_to_floor_3() +} + +type Steps = usize; +type Floor = usize; +type Element = usize; +#[derive(PartialEq, Debug)] +struct Microchip { + element: Element, +} +impl Microchip { + fn matches(&self, generator: &Generator) -> bool { + self.element == generator.element + } + // A chip is safe if there are no generators, or if its matching generator is nearby + fn is_safe(&self, generators: &[Generator]) -> bool { + generators.is_empty() || generators.iter().any(|other| self.matches(other)) + } +} +#[derive(PartialEq, Debug)] +struct Generator { + element: Element, +} + +#[derive(Debug, PartialEq, Eq)] +struct State { + steps: Steps, + facility: Facility, +} +impl State { + fn take_elevator( + &self, + gens: &[&Generator], + chips: &[&Microchip], + next_floor: Floor, + ) -> Option { + let facility = self.facility.take_elevator(gens, chips, next_floor); + if facility.is_safe() { + Some(State { + steps: self.steps + 1, + facility, + }) + } else { + None + } + } +} +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + // Prefer fewer steps, or higher elevator + // (because the goal is to move everything to the top floor) + match self.steps.cmp(&other.steps) { + Ordering::Equal => self + .facility + .elevator_floor + .cmp(&other.facility.elevator_floor), + steps => steps.reverse(), + } + } +} + +#[derive(Hash, PartialEq, Eq, Clone, Debug)] +struct Facility { + generators: Vec, + microchips: Vec, + elevator_floor: Floor, +} +impl From> for Facility { + fn from(input: Vec<&str>) -> Self { + let mut elements = vec![]; + input + .iter() + .filter(|line| line.contains("compatible")) + .for_each(|line| { + line.split(|c| c == ' ' || c == '-') + .collect::>() + .windows(2) + .for_each(|pair| { + if pair[1] == "compatible" { + elements.push(pair[0]); + } + }) + }); + let mut generators = vec![0; elements.len()]; + let separators = |c| c == ' ' || c == '-' || c == ',' || c == '.'; + let element_idx = |s| elements.iter().position(|&el| el == s).unwrap(); + + input.iter().enumerate().for_each(|(floor, line)| { + line.split(separators) + .collect::>() + .windows(2) + .for_each(|pair| { + if pair[1] == "generator" { + generators[element_idx(pair[0])] = floor; + } + }) + }); + + let mut microchips = vec![0; elements.len()]; + input.iter().enumerate().for_each(|(floor, line)| { + line.split(separators) + .collect::>() + .windows(3) + .for_each(|triple| { + if triple[2] == "microchip" { + microchips[element_idx(triple[0])] = floor; + } + }) + }); + Facility { + generators, + microchips, + elevator_floor: 0, + } + } +} +impl Facility { + fn state(&self) -> Vec { + let mut counts = vec![self.elevator_floor]; + for floor in 0..4 { + counts.push(self.microchips_on(&floor).len()); + counts.push(self.generators_on(&floor).len()); + } + counts + } + fn is_safe(&self) -> bool { + for floor in 0..4 { + let generators = self.generators_on(&floor); + if !self + .microchips_on(&floor) + .into_iter() + .all(|chip| chip.is_safe(&generators)) + { + return false; + } + } + true + } + fn take_elevator( + &self, + gens: &[&Generator], + chips: &[&Microchip], + next_floor: Floor, + ) -> Facility { + let mut next = self.clone(); + next.elevator_floor = next_floor; + for gen in gens { + next.generators[gen.element] = next_floor; + } + for chip in chips { + next.microchips[chip.element] = next_floor; + } + next + } + fn element_count(&self) -> usize { + self.generators.len() + } + fn is_done(&self) -> bool { + self.elevator_floor == 3 + && self.microchips_on_elevator_floor().len() == self.element_count() + && self.generators_on_elevator_floor().len() == self.element_count() + } + fn steps_to_bring_everything_to_floor_3(&mut self) -> usize { + let mut states: BinaryHeap = BinaryHeap::new(); + states.push(State { + facility: self.clone(), + steps: 0, + }); + let mut seen = HashSet::new(); + while let Some(curr) = states.pop() { + // println!("\nPopped state {:?}", curr); + let state = curr.facility.state(); + if seen.contains(&state) { + continue; + } else { + seen.insert(state); + } + if curr.facility.is_done() { + return curr.steps; + } + // We can change one floor at a time + for next in curr.facility.next_floor_choices() { + // Bring a generator by itself + for gen in curr.facility.generators_on_elevator_floor() { + if let Some(next) = curr.take_elevator(&[&gen], &[], next) { + states.push(next) + } + + // Bring a generator and a microchip + for chip in curr.facility.microchips_on_elevator_floor() { + if let Some(next) = curr.take_elevator(&[&gen], &[&chip], next) { + states.push(next) + } + } + + // Bring two generators + for gen2 in curr.facility.generators_on_elevator_floor() { + if gen2 != gen { + if let Some(next) = curr.take_elevator(&[&gen, &gen2], &[], next) { + states.push(next) + } + } + } + } + + // Bring a microchip by itself + for chip in curr.facility.microchips_on_elevator_floor() { + if let Some(next) = curr.take_elevator(&[], &[&chip], next) { + states.push(next) + } + + // Bring two microchips + for chip2 in curr.facility.microchips_on_elevator_floor() { + if chip2 != chip { + if let Some(next) = curr.take_elevator(&[], &[&chip, &chip2], next) { + states.push(next) + } + } + } + } + } + } + unreachable!() + } + fn next_floor_choices(&self) -> Vec { + match self.elevator_floor { + 0 => vec![1], + 1 => vec![0, 2], + 2 => vec![1, 3], + 3 => vec![2], + _ => unreachable!(), + } + } + fn generators_on_elevator_floor(&self) -> Vec { + self.generators_on(&self.elevator_floor) + } + fn generators_on(&self, other: &Floor) -> Vec { + self.generators + .iter() + .enumerate() + .filter(|(_, floor)| floor == &other) + .map(|(element, _)| Generator { element }) + .collect() + } + fn microchips_on_elevator_floor(&self) -> Vec { + self.microchips_on(&self.elevator_floor) + } + fn microchips_on(&self, other: &Floor) -> Vec { + self.microchips + .iter() + .enumerate() + .filter(|(_, floor)| floor == &other) + .map(|(element, _)| Microchip { element }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +The first floor contains a hydrogen-compatible microchip and a lithium-compatible microchip. +The second floor contains a hydrogen generator. +The third floor contains a lithium generator. +The fourth floor contains nothing relevant."; + + #[test] + fn part1_example() { + let mut facility = Facility::from(parse(EXAMPLE)); + assert_eq!(11, facility.steps_to_bring_everything_to_floor_3()); + } + + #[test] + fn part1() { + assert_eq!(37, day11_part1()); + } + + #[test] + fn part2() { + assert_eq!(61, day11_part2()); + } +} diff --git a/2016/src/day12.rs b/2016/src/day12.rs new file mode 100644 index 0000000..cdf4680 --- /dev/null +++ b/2016/src/day12.rs @@ -0,0 +1,46 @@ +use crate::assembunny::Computer; +use crate::parse; + +const INPUT: &str = include_str!("../input/day12.txt"); + +pub(crate) fn day12_part1() -> isize { + let mut computer = Computer::from(parse(INPUT)); + computer.run() +} + +pub(crate) fn day12_part2() -> isize { + let mut computer = Computer::from(parse(INPUT)); + computer.set_register('c', 1); + computer.run() +} + +#[cfg(test)] +mod tests { + use crate::parse; + + use super::*; + + const EXAMPLE: &str = "\ +cpy 41 a +inc a +inc a +dec a +jnz a 2 +dec a"; + + #[test] + fn part1_example() { + let mut computer = Computer::from(parse(EXAMPLE)); + assert_eq!(42, computer.run()); + } + + #[test] + fn part1() { + assert_eq!(318_003, day12_part1()); + } + + #[test] + fn part2() { + assert_eq!(9_227_657, day12_part2()); + } +} diff --git a/2016/src/day13.rs b/2016/src/day13.rs new file mode 100644 index 0000000..d156eb4 --- /dev/null +++ b/2016/src/day13.rs @@ -0,0 +1,174 @@ +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashSet}; +const PUZZLE_INPUT: usize = 1350; + +pub(crate) fn day13_part1() -> usize { + shortest_path(Coord::default(), Coord { x: 31, y: 39 }, PUZZLE_INPUT) +} + +pub(crate) fn day13_part2() -> usize { + reachable_with_steps(Coord::default(), 50, PUZZLE_INPUT) +} + +type MagicNumber = usize; + +#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] +struct Coord { + x: usize, + y: usize, +} +impl Default for Coord { + fn default() -> Self { + Coord { x: 1, y: 1 } + } +} +impl Coord { + fn is_open(&self, fav: MagicNumber) -> bool { + is_open(self.x, self.y, fav) + } + fn offset_by(&self, x: isize, y: isize) -> Option { + let x = self.x as isize + x; + let y = self.y as isize + y; + if x >= 0 && y >= 0 { + Some(Coord { + x: x as usize, + y: y as usize, + }) + } else { + None + } + } + fn neighbors(&self) -> Vec { + vec![ + self.offset_by(1, 0), + self.offset_by(0, 1), + self.offset_by(-1, 0), + self.offset_by(0, -1), + ] + .into_iter() + .flatten() + .collect() + } +} + +fn is_open(x: usize, y: usize, fav: MagicNumber) -> bool { + let n = x * x + 3 * x + 2 * x * y + y + y * y + fav; + n.count_ones() % 2 == 0 +} + +#[derive(Eq, PartialEq, Debug)] +struct State { + count: usize, + pos: Coord, +} +impl State { + fn new(count: usize, pos: Coord) -> Self { + State { count, pos } + } +} +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + // fewer steps is better + self.count.cmp(&other.count).reverse() + } +} +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn shortest_path(from: Coord, to: Coord, fav: MagicNumber) -> usize { + let mut next = BinaryHeap::new(); + next.push(State { + count: 0, + pos: from, + }); + let mut visited = HashSet::new(); + while let Some(State { count, pos }) = next.pop() { + if pos == to { + return count; + } + if !visited.insert(pos) { + continue; + } + for neighbor in pos.neighbors() { + if neighbor.is_open(fav) { + next.push(State::new(count + 1, neighbor)); + } + } + } + unreachable!() +} + +fn reachable_with_steps(from: Coord, max_steps: usize, fav: MagicNumber) -> usize { + let mut next = BinaryHeap::new(); + next.push(State { + count: 0, + pos: from, + }); + let mut visited = HashSet::new(); + while let Some(State { count, pos }) = next.pop() { + if !visited.insert(pos) { + continue; + } + for neighbor in pos.neighbors() { + if neighbor.is_open(fav) && count < max_steps { + next.push(State::new(count + 1, neighbor)); + } + } + } + visited.len() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example_grid() { + let grid = create_grid(10, 7, 10); + assert_eq!( + grid, + "\ +.#.####.## +..#..#...# +#....##... +###.#.###. +.##..#..#. +..##....#. +#...##.###" + ); + } + + fn create_grid(width: usize, height: usize, fav: MagicNumber) -> String { + (0..height) + .into_iter() + .map(|y| { + (0..width) + .into_iter() + .map(|x| if is_open(x, y, fav) { '.' } else { '#' }) + .collect::() + }) + .collect::>() + .join("\n") + } + + #[test] + fn part1_example_shortest_path() { + let from = Coord::default(); + let to = Coord { x: 7, y: 4 }; + let fav = 10; + assert_eq!(11, shortest_path(from, to, fav)); + } + + #[test] + fn part1() { + assert_eq!(92, day13_part1()); + } + + #[test] + fn part2() { + assert_eq!(124, day13_part2()); + } +} diff --git a/2016/src/day14.rs b/2016/src/day14.rs new file mode 100644 index 0000000..5f572f7 --- /dev/null +++ b/2016/src/day14.rs @@ -0,0 +1,296 @@ +use md5::Digest; +use rayon::prelude::*; +use std::collections::HashMap; + +const PUZZLE_INPUT: &str = "ahsbgdzn"; + +pub(crate) fn day14_part1() -> usize { + index_of_64th_key_part1(PUZZLE_INPUT) +} + +pub(crate) fn day14_part2() -> usize { + index_of_64th_key_part2(PUZZLE_INPUT) +} + +// First attempt used for part 1. It works but is really inefficient, +// because it calculates the same hash multiple times +#[allow(unused)] +fn index_of_64th_key_part_naive_brute_force(salt: &str) -> usize { + let single_core = true; + if single_core { + (0..usize::MAX) + .into_iter() + .filter(|i| is_part1_key(salt, *i)) + .take(64) // too bad rayon can't also use take(64) ;) + .last() + .unwrap() + } else { + // On my 9900K (8-cores, 16-threads), a multiplier of 100 is about as fast as it gets. + // Too little makes it slower, and too much would find unnecessarily many keys + let step_size = 16 * 100; + let mut start = 0; + let mut all_keys = vec![]; + while all_keys.len() < 64 { + let mut keys = (start..(start + step_size)) + .into_par_iter() + .filter(|i| is_part1_key(salt, *i)) + .collect(); + all_keys.append(&mut keys); + + start += step_size; + } + all_keys[63] + } +} + +fn index_of_64th_key_part1(salt: &str) -> usize { + index_of_64th_key(salt, true) +} +fn index_of_64th_key_part2(salt: &str) -> usize { + index_of_64th_key(salt, false) +} +fn index_of_64th_key(salt: &str, part1: bool) -> usize { + let hash_func = if part1 { md5 } else { stretched_md5 }; + let mut len5_indices: Vec = vec![]; + let single_core = false; + if single_core { + let mut len3_chars_by_index: HashMap = HashMap::new(); + let mut i5 = 0; + while len5_indices.len() < 64 || len5_indices[63] + 1000 > i5 { + let hash = hash_func(salt, i5); + if let Some(c3) = first_char_appearing_3_times_in_a_row(hash) { + len3_chars_by_index.insert(i5, c3); + } + if let Some(c5) = first_char_appearing_5_times_in_a_row(hash) { + let mut i3s = index_of_matching(&len3_chars_by_index, c5, i5); + len5_indices.append(&mut i3s); + } + i5 += 1; + len5_indices.sort_unstable(); + } + } else { + let step_size = 16; // (Multiple of) 16 because i9 9900K has 8-cores with 16 threads + let mut i5 = 0; + let mut triples: HashMap = HashMap::new(); + while len5_indices.len() < 64 || len5_indices[63] + 1000 > i5 { + let hashes3: Vec<_> = (i5..(i5 + step_size)) + .into_par_iter() // around 3 minutes single-core, or 23s in parallel + .filter_map(|i5| { + let hash = if part1 { + md5(salt, i5) + } else { + stretched_md5(salt, i5) + }; + first_char_appearing_3_times_in_a_row(hash).map(|c3| (i5, c3, hash)) + }) + .collect(); + let mut hashes5 = vec![]; + for (i5, c3, hash) in hashes3 { + triples.insert(i5, c3); + if let Some(c5) = first_char_appearing_5_times_in_a_row(hash) { + hashes5.push((i5, c5)); + } + } + hashes5.into_iter().for_each(|(i5, c5)| { + let mut triples = index_of_matching(&triples, c5, i5); + len5_indices.append(&mut triples); + }); + i5 += step_size; + } + } + len5_indices[63] +} + +fn index_of_matching(triples: &HashMap, wanted: char, end: usize) -> Vec { + let start = end.saturating_sub(1000); + // println!( + // "5-tuple of '{}' @ {}, looking for triple in {:?}", + // wanted, + // end, + // start..end - 1 + // ); + (start..end) + .into_iter() + .filter_map(|i| { + triples.get(&i).filter(|&&c| c == wanted).map(|_c| { + // println!("- 3er '{}' @ {}", _c, i); + i + }) + }) + .collect() +} + +fn is_part1_key(salt: &str, i: usize) -> bool { + is_key(salt, i, &md5) +} + +fn is_key(salt: &str, i: usize, hash_func: &dyn Fn(&str, usize) -> Digest) -> bool { + if let Some(wanted) = first_char_appearing_3_times_in_a_row(hash_func(salt, i)) { + wanted.appears_5_times_in_a_row_within_next_1000_hashes(i + 1, salt, hash_func) + } else { + false + } +} + +fn first_char_appearing_3_times_in_a_row(hash: Digest) -> Option { + first_char_appearing_n_times_in_a_row(hash, 3) +} + +fn first_char_appearing_5_times_in_a_row(hash: Digest) -> Option { + first_char_appearing_n_times_in_a_row(hash, 5) +} + +fn first_char_appearing_n_times_in_a_row(hash: Digest, n: usize) -> Option { + hash.to_char_vec() + .windows(n) + .find(are_all_equal) + .map(|w| w[0]) +} + +fn md5(salt: &str, i: usize) -> Digest { + md5::compute(format!("{}{}", salt, i)) +} + +fn stretched_md5(salt: &str, i: usize) -> Digest { + let mut hash = md5(salt, i); + for _ in 0..2016 { + hash = md5::compute(format!("{:x}", hash)); + } + hash +} + +trait ToCharVec { + fn to_char_vec(&self) -> Vec; +} +impl ToCharVec for Digest { + fn to_char_vec(&self) -> Vec { + format!("{:x}", self).chars().collect() + } +} + +trait Appears5TimesInARowWithinNext1000Hashes { + fn appears_5_times_in_a_row_within_next_1000_hashes( + &self, + start: usize, + salt: &str, + hash_func: &dyn Fn(&str, usize) -> Digest, + ) -> bool; +} +impl Appears5TimesInARowWithinNext1000Hashes for char { + fn appears_5_times_in_a_row_within_next_1000_hashes( + &self, + start: usize, + salt: &str, + hash: &dyn Fn(&str, usize) -> Digest, + ) -> bool { + (start..start + 1000) + .into_iter() + .any(|i| hash(salt, i).contains_5_in_a_row(*self)) + } +} + +trait Contains5InARow { + fn contains_5_in_a_row(&self, wanted: T) -> bool; +} +impl Contains5InARow for Digest { + fn contains_5_in_a_row(&self, wanted: char) -> bool { + self.to_char_vec() + .windows(5) + .any(|w| wanted == w[0] && are_all_equal(&w)) + } +} + +fn are_all_equal(w: &&[char]) -> bool { + w.iter().skip(1).all(|c| c == &w[0]) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SALT: &str = "abc"; + + #[test] + fn part1_example_index_18_is_a_triple_but_no_key() { + let index = 18; + let hash = md5(SALT, index); + assert_eq!(Some('8'), first_char_appearing_3_times_in_a_row(hash)); + assert!(!'8'.appears_5_times_in_a_row_within_next_1000_hashes(index + 1, SALT, &md5)); + } + + #[test] + fn part1_example_index_39_is_a_triple_and_a_key() { + let index = 39; + let hash = md5(SALT, index); + assert_eq!(Some('e'), first_char_appearing_3_times_in_a_row(hash)); + assert!('e'.appears_5_times_in_a_row_within_next_1000_hashes(index + 1, SALT, &md5)); + assert!(is_part1_key(SALT, index)); + } + + #[test] + fn part1_example_third_and_last_keys() { + assert!(is_part1_key(SALT, 92)); + assert!(is_part1_key(SALT, 22728)); + } + + // Simple brute force approach takes around ~20s single core, < 3s multi-core + // With the better part 2 approach it's less than 500ms + #[test] + fn part1_example() { + assert_eq!(22728, index_of_64th_key_part1(SALT)); + } + + // Simple brute force approach takes around ~22s single core, < 3s multi-core + // With the better part 2 approach it's less than 500ms + #[test] + fn part1() { + assert_eq!(23_890, day14_part1()); + } + + #[test] + fn part2_example_stretched_hash() { + assert_eq!( + "a107ff634856bb300138cac6568c0f24", + format!("{:x}", stretched_md5(SALT, 0)) + ); + } + + #[test] + fn part2_example_index_5_is_a_triple_but_no_key() { + let index = 5; + let hash = stretched_md5(SALT, index); + assert_eq!(Some('2'), first_char_appearing_3_times_in_a_row(hash)); + assert!(!'2'.appears_5_times_in_a_row_within_next_1000_hashes( + index + 1, + SALT, + &stretched_md5 + )); + } + + #[test] + fn part2_example_index_10_is_a_triple_and_a_key() { + let index = 10; + let hash = stretched_md5(SALT, index); + assert_eq!(Some('e'), first_char_appearing_3_times_in_a_row(hash)); + assert!('e'.appears_5_times_in_a_row_within_next_1000_hashes( + index + 1, + SALT, + &stretched_md5 + )); + assert!(is_part2_key(SALT, index)); + } + + fn is_part2_key(salt: &str, i: usize) -> bool { + is_key(salt, i, &stretched_md5) + } + + #[test] + fn part2_example() { + assert_eq!(22_551, index_of_64th_key_part2(SALT)); + } + + #[test] + fn part2() { + assert_eq!(22_696, day14_part2()); + } +} diff --git a/2016/src/day15.rs b/2016/src/day15.rs new file mode 100644 index 0000000..499f754 --- /dev/null +++ b/2016/src/day15.rs @@ -0,0 +1,115 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day15.txt"); + +pub(crate) fn day15_part1() -> usize { + let input = parse(INPUT); + let discs = discs_from(input); + earliest_start_time_with_full_alignment(discs) +} + +pub(crate) fn day15_part2() -> usize { + let input = parse(INPUT); + let mut discs = discs_from(input); + let period = 11; + let t0pos = 0; + discs.push(Disc { + period, + _t0pos: t0pos, + time: Disc::first_pass_through_start_time(discs.len() + 1, t0pos, period), + }); + earliest_start_time_with_full_alignment(discs) +} + +fn earliest_start_time_with_full_alignment(discs: Vec) -> usize { + // The disc with the most positions takes the longest to re-align, + // it's pointless to try more often than this slowest rotation period + let (mut slowest, mut others) = split(discs); + while !slowest.is_aligned_with(&others) { + slowest.rotate_once(); + for disc in others.iter_mut() { + while disc.time < slowest.time { + disc.rotate_once(); + } + } + } + slowest.time +} + +fn split(mut discs: Vec) -> (Disc, Vec) { + discs.sort_unstable_by_key(|disc| disc.period); + let slowest = discs.pop().unwrap(); + (slowest, discs) +} + +fn discs_from(input: Vec<&str>) -> Vec { + input.into_iter().map(Disc::from).collect() +} + +#[derive(Debug)] +struct Disc { + period: usize, + _t0pos: usize, + time: usize, +} +impl From<&str> for Disc { + fn from(s: &str) -> Self { + // Disc #1 has 13 positions; at time=0, it is at position 1. + let s: Vec<_> = s.split(|c| c == ' ' || c == '#' || c == '.').collect(); + let number = s[2].parse().unwrap(); + let period = s[4].parse().unwrap(); + let t0pos = s[12].parse().unwrap(); + let time = Disc::first_pass_through_start_time(number, t0pos, period); + Disc { + period, + _t0pos: t0pos, + time, + } + } +} +impl Disc { + fn first_pass_through_start_time(number: usize, t0pos: usize, period: usize) -> usize { + // It takes ${number} of seconds to reach this disc + let mut t = number; + // After this, a capsule can pass through every ${period} seconds + while (t0pos + t) % period != 0 { + t += 1; + } + // Subtract ${number} seconds to get the start time at the top + t - number + } + fn rotate_once(&mut self) { + self.time += self.period; + } + fn is_aligned_with(&self, others: &[Disc]) -> bool { + others.iter().all(|disc| disc.time == self.time) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::parse; + + const EXAMPLE: &str = "\ +Disc #1 has 5 positions; at time=0, it is at position 4. +Disc #2 has 2 positions; at time=0, it is at position 1."; + + #[test] + fn part1_example() { + let input = parse(EXAMPLE); + let discs = discs_from(input); + assert_eq!(5, earliest_start_time_with_full_alignment(discs)); + } + + #[test] + fn part1() { + assert_eq!(376_777, day15_part1()); + } + + #[test] + fn part2() { + assert_eq!(3_903_937, day15_part2()); + } +} diff --git a/2016/src/day16.rs b/2016/src/day16.rs new file mode 100644 index 0000000..3a3d2de --- /dev/null +++ b/2016/src/day16.rs @@ -0,0 +1,298 @@ +const PUZZLE_INPUT: &str = "11100010111110100"; + +pub(crate) fn day16_part1() -> String { + checksum_of_data_generated_to_len(PUZZLE_INPUT, 272) +} + +pub(crate) fn day16_part2() -> String { + checksum_of_data_generated_to_len(PUZZLE_INPUT, 35_651_584) +} + +enum Method { + #[allow(unused)] + Strings, + #[allow(unused)] + BoolVec, + Fast, +} +fn checksum_of_data_generated_to_len(input: &str, min_len: usize) -> String { + let method = Method::Fast; + match method { + Method::Strings => { + let data = generate_string_of_length(input, min_len); + calc_checksum_of_string(&data[0..min_len]) + } + Method::BoolVec => { + let data = generate_boolvec_of_length(input, min_len); + calc_checksum_of_boolvec(&data[0..min_len]) + } + Method::Fast => faster_checksum_of_data_generated_to_len(input, min_len), + } +} +fn faster_checksum_of_data_generated_to_len(input: &str, min_len: usize) -> String { + // Let's call the input sequence a. One step of lengthening appends 0 to this sequence, + // as well as its reversed negation. Let's call this reversed_negation(a) == b, so the + // complete sequence after one step looks like a_0_b (underscores are for formatting only). + // The next lengthening step does the same thing: it appends 0 and reversed_negation(a0b). + // Interestingly, the reversed_negation(a0b) is equal to a1b! So the complete sequence + // after 2 steps is a0b_0_a1b. So basically it's a sequence of alternating a and b separated + // by 0s and 1s. If we focus on the 0s and 1s only this would be 001. + // After 3 steps it's a0b_0_a1b_0_a0b_1_a1b, or 001_0_011. + // After 4 steps a0b0a1b_0_a0b1a1b_0_a0b0a1b_1_a0b1a1b; or 001_0_011_0_001_1_011 + // After 5 steps a0b0a1b0a0b1a1b_0_a0b0a1b1a0b1a1b_0_a0b0a1b0a0b1a1b_1_a0b0a1b1a0b1a1b; + // or 0010011_0_0011011_0_0010011_1_0011011 + // Notice how the right half (after the center 0) consists of the same two parts as the + // left half, but with a 1 as a separator on the right instead of the 0 separator on the left. + // Just the separators, with the center marked as |0| and left _0_ and right _1_: + // After 1 step : |0| + // After 2 steps: 0 |0| 1 + // After 3 steps: 0_0_1 |0| 0_1_1 + // After 4 steps: 001_0_011 |0| 001_1_011 + // After 5 steps: 0010011_0_0011011 |0| 0010011_1_0011011 + // After 6 steps: 001001100011011_0_001001110011011|0|001001100011011_1_001001110011011 + + if let Ok(a) = usize::from_str_radix(input, 2) { + // Let's calculate the number of lengthening steps i to reach the wanted length + // total length = length of the inputs (2^i * input.len()) plus separator length (2^i - 1) + let tot_len = |i| 2usize.pow(i) * (input.len() + 1) - 1; + let mut i = 0; + // left and right separators + let mut left = vec![]; + let mut right = vec![]; + while tot_len(i) < min_len { + i += 1; + match i { + 0..=1 => { /*noop*/ } + 2 => { + // init + left.push(false); + right.push(true); + } + _ => { + left.push(false); + left.append(&mut right); + right = left.clone(); + // right is the same as left, but with the middle bit 1 instead of 0 + right[left.len() / 2] = true; + } + } + } + left.push(false); + left.append(&mut right); + // This extra separator makes sure that the last part of the input sequence will be + // processed within the for-loop. It will not actually be used, and can be set to whatever. + left.push(true); + let separators = left; + + // The checksum of this sequence will reduce the sequence back to the original input length, + // and each bit of the final checksum is a result of i checksum passes, over a part of the + // sequence that is 2^i bits long. The final 2^i - 1 bits of this sequence will be discarded, + // as this part is just one bit too short to result in a checksum bit of its own. + + // Examples with an input length of 17 bits: + // - For min_len 272 there are 4 steps to reach a total length of 287. + // Each of the 17 checksum bits result from 2^4 = 16 bits of the sequence (17 * 16 = 272), + // and the final 15 bits are discarded (272 + 15 = 287) + // - For min_len 35'651'584 there are 21 steps to reach a total length of 37'748'735. + // Each checksum bit uses 2^21 = 2'097'152 bits of the sequence (17 * 2'097'152 = 35'651'584), + // and the final 2'097'151 bits are discarded. + + let b = !a.reverse_bits() >> (64 - input.len()); + // println!("a = {:0len$b}, b = {:0len$b}", a, b, len = input.len()); + + let wanted_bit_count = 2usize.pow(i); + let mut actual_bit_count = 0; + + // Interestingly, the checksum of a sequence of at least 4 bits can be simplified to counting + // the sequence's ones and zeroes. If their counts are even, the checksum is 1, otherwise 0. + // For example: + // 4-bit sequences yielding a checksum of 1 have four 0s and no 1s, or vice versa, or two of each: + // - 0000, 1111, 0011, 1100 -> 11 -> 1 + // - 0101, 1010, 0110, 1001 -> 00 -> 1 + // 4-bit sequences yielding a checksum of 0 have a single 0 and three 1s, or vice versa: + // - 1000, 0100, 0111, 1011 -> 01 -> 0 + // - 0010, 0001, 1101, 1110 -> 10 -> 0 + let mut checksum = vec![]; + let mut ones_count = 0; + + let bits_per_usize = usize::MAX.count_ones() as usize; + 'FOR_LOOP: for (i, sep) in separators.into_iter().enumerate() { + let sep = if sep { 1 } else { 0 }; + let mut num = if i % 2 == 0 { a } else { b }; + num <<= 1; + num |= sep; + + let mut num_bit_count = input.len() + 1; + // println!("num = {:0len$b}", num, len = num_bit_count); + + while actual_bit_count + num_bit_count >= wanted_bit_count { + let needed_bit_count = wanted_bit_count - actual_bit_count; + + num_bit_count -= needed_bit_count; + let num_copy = num; + + num >>= num_bit_count; + ones_count += num.count_ones(); + checksum.push(ones_count % 2 == 0); + ones_count = 0; + actual_bit_count = 0; + + if checksum.len() >= input.len() { + break 'FOR_LOOP; + } + + if num_bit_count == 0 { + // avoid shift left with overflow + num = 0; + } else { + let mut leftover_bits = num_copy; + leftover_bits <<= bits_per_usize - num_bit_count; + leftover_bits >>= bits_per_usize - num_bit_count; + num = leftover_bits; + }; + } + ones_count += num.count_ones(); + actual_bit_count += num_bit_count; + } + + checksum.to_string() + } else { + panic!("Invalid input {}", input); + } +} + +fn generate_string_of_length(input: &str, min_len: usize) -> String { + let mut output = input.to_string(); + while output.len() < min_len { + output = lengthen_string(&output); + } + output +} + +fn generate_boolvec_of_length(input: &str, min_len: usize) -> Vec { + let mut output = input.to_boolvec(); + while output.len() < min_len { + output = lengthen_boolvec(output); + } + output +} +trait ToBoolVec { + fn to_boolvec(&self) -> Vec; +} +impl ToBoolVec for &str { + fn to_boolvec(&self) -> Vec { + self.chars().map(|c| c == '1').collect() + } +} +trait BoolVecToString { + fn to_string(&self) -> String; +} +impl BoolVecToString for Vec { + fn to_string(&self) -> String { + self.iter().map(|on| if *on { '1' } else { '0' }).collect() + } +} + +fn lengthen_string>(input: T) -> String { + let output: String = input + .as_ref() + .chars() + .rev() + .map(|c| if c == '1' { '0' } else { '1' }) + .collect(); + + format!("{}0{}", input.as_ref(), output) +} + +fn lengthen_boolvec(input: Vec) -> Vec { + let mut right: Vec<_> = input.iter().rev().map(|b| !*b).collect(); + let mut output = input; + output.push(false); + output.append(&mut right); + output +} + +fn calc_checksum_of_string(s: &str) -> String { + let mut checksum = reduce_string(s); + while checksum.len() % 2 == 0 { + checksum = reduce_string(&checksum); + } + checksum +} + +fn calc_checksum_of_boolvec(input: &[bool]) -> String { + let mut checksum = reduce_boolvec(input); + while checksum.len() % 2 == 0 { + checksum = reduce_boolvec(&checksum); + } + checksum.to_string() +} + +fn reduce_string(s: &str) -> String { + s.chars() + .collect::>() + .windows(2) + .step_by(2) + .map(|pair| if pair[0] == pair[1] { '1' } else { '0' }) + .collect() +} + +fn reduce_boolvec(bv: &[bool]) -> Vec { + bv.windows(2).step_by(2).map(|p| p[0] == p[1]).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lengthen_data() { + assert_eq!("100", lengthen_string("1")); + assert_eq!("001", lengthen_string("0")); + assert_eq!("11111000000", lengthen_string("11111")); + assert_eq!("1111000010100101011110000", lengthen_string("111100001010")); + } + + #[test] + fn test_generate_string_of_length() { + assert_eq!( + "10000011110010000111110", + generate_string_of_length("10000", 20) + ); + } + + #[test] + fn test_calc_checksum_of_string() { + assert_eq!("100", calc_checksum_of_string("110010110100")); + } + #[test] + fn test_generate_boolvec_of_length() { + assert_eq!( + "10000011110010000111110".to_boolvec(), + generate_boolvec_of_length("10000", 20) + ); + } + + #[test] + fn test_calc_checksum_of_boolvec() { + assert_eq!( + "100", + calc_checksum_of_boolvec(&"110010110100".to_boolvec()) + ); + } + + #[test] + fn test_checksum_of_data_generated_to_len() { + assert_eq!("01100", checksum_of_data_generated_to_len("10000", 20)); + } + + #[test] + fn part1() { + assert_eq!("10100011010101011", day16_part1()); + } + + #[test] + fn part2() { + assert_eq!("01010001101011001", day16_part2()); + } +} diff --git a/2016/src/day17.rs b/2016/src/day17.rs new file mode 100644 index 0000000..5be1839 --- /dev/null +++ b/2016/src/day17.rs @@ -0,0 +1,245 @@ +use md5::Digest; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +const PUZZLE_INPUT: &str = "hhhxzeay"; + +pub(crate) fn day17_part1() -> String { + shortest_path(PUZZLE_INPUT) +} + +pub(crate) fn day17_part2() -> usize { + longest_path_len(PUZZLE_INPUT) +} + +#[derive(Debug, PartialEq, Eq, Clone)] +enum Direction { + Up, + Down, + Left, + Right, +} +impl Direction { + fn is_possible_at(&self, pos: &Pos) -> bool { + match self { + Direction::Up => pos.y > 0, + Direction::Down => pos.y < 3, + Direction::Left => pos.x > 0, + Direction::Right => pos.x < 3, + } + } + fn with_open_doors(x: &[char]) -> Vec { + let mut dirs = vec![]; + if x[0].is_open() { + dirs.push(Direction::Up); + } + if x[1].is_open() { + dirs.push(Direction::Down); + } + if x[2].is_open() { + dirs.push(Direction::Left); + } + if x[3].is_open() { + dirs.push(Direction::Right); + } + dirs + } +} +impl ToString for Direction { + fn to_string(&self) -> String { + match self { + Direction::Up => "U", + Direction::Down => "D", + Direction::Left => "L", + Direction::Right => "R", + } + .to_string() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +struct Pos { + x: u8, + y: u8, +} + +impl Pos { + fn is_at_target(&self) -> bool { + self.x == 3 && self.y == 3 + } + fn move_into(&self, dir: &Direction) -> Pos { + let (x, y) = (self.x, self.y); + match dir { + Direction::Up => Pos { x, y: y - 1 }, + Direction::Down => Pos { x, y: y + 1 }, + Direction::Left => Pos { x: x - 1, y }, + Direction::Right => Pos { x: x + 1, y }, + } + } +} + +impl Default for Pos { + fn default() -> Self { + Pos { x: 0, y: 0 } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +struct State { + pos: Pos, + path: Vec, +} +impl State { + fn reachable_neighbors(&self, pass_code: &str) -> Vec { + let mut states = vec![]; + let header = md5(pass_code, &self.to_string()).to_char_vec(); + + for dir in Direction::with_open_doors(&header) + .into_iter() + .filter(|dir| dir.is_possible_at(&self.pos)) + { + let mut next = self.clone(); + next.pos = self.pos.move_into(&dir); + next.path.push(dir); + states.push(next); + } + states + } +} +impl ToString for State { + fn to_string(&self) -> String { + self.path.iter().map(Direction::to_string).collect() + } +} +impl Default for State { + fn default() -> Self { + State { + pos: Pos::default(), + path: Vec::default(), + } + } +} +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + self.path.len().cmp(&other.path.len()).reverse() + } +} +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn shortest_path(pass_code: &str) -> String { + let mut candidates = BinaryHeap::new(); + candidates.push(State::default()); + while let Some(state) = candidates.pop() { + if state.pos.is_at_target() { + return state.to_string(); + } else { + candidates.extend(state.reachable_neighbors(pass_code)); + } + } + unreachable!() +} + +fn longest_path_len(pass_code: &str) -> usize { + let mut candidates = vec![State::default()]; + let mut max_len = 0; + while let Some(state) = candidates.pop() { + if state.pos.is_at_target() { + max_len = max_len.max(state.path.len()); + } else { + candidates.extend(state.reachable_neighbors(pass_code)); + } + } + max_len +} + +fn md5(pass_code: &str, path: &str) -> Digest { + md5::compute(format!("{}{}", pass_code, path)) +} + +trait ToCharVec { + fn to_char_vec(&self) -> Vec; +} +impl ToCharVec for Digest { + fn to_char_vec(&self) -> Vec { + format!("{:x}", self).chars().take(4).collect() + } +} + +trait IsOpen { + fn is_open(&self) -> bool; +} +impl IsOpen for char { + fn is_open(&self) -> bool { + ('b'..='f').contains(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_open() { + assert!(!'0'.is_open()); + assert!(!'1'.is_open()); + assert!(!'2'.is_open()); + assert!(!'3'.is_open()); + assert!(!'4'.is_open()); + assert!(!'5'.is_open()); + assert!(!'6'.is_open()); + assert!(!'7'.is_open()); + assert!(!'8'.is_open()); + assert!(!'9'.is_open()); + assert!(!'a'.is_open()); + assert!('b'.is_open()); + assert!('c'.is_open()); + assert!('d'.is_open()); + assert!('e'.is_open()); + assert!('f'.is_open()); + assert!(!'g'.is_open()); + } + + #[test] + fn part1_example1() { + assert_eq!("DDRRRD", shortest_path("ihgpwlah")); + } + + #[test] + fn part1_example2() { + assert_eq!("DDUDRLRRUDRD", shortest_path("kglvqrro")); + } + + #[test] + fn part1_example3() { + assert_eq!("DRURDRUDDLLDLUURRDULRLDUUDDDRR", shortest_path("ulqzkmiv")); + } + + #[test] + fn part1() { + assert_eq!("DDRUDLRRRD", day17_part1()); + } + + #[test] + fn part2_example1() { + assert_eq!(370, longest_path_len("ihgpwlah")); + } + + #[test] + fn part2_example2() { + assert_eq!(492, longest_path_len("kglvqrro")); + } + + #[test] + fn part2_example3() { + assert_eq!(830, longest_path_len("ulqzkmiv")); + } + + #[test] + fn part2() { + assert_eq!(398, day17_part2()); + } +} diff --git a/2016/src/day18.rs b/2016/src/day18.rs new file mode 100644 index 0000000..1531270 --- /dev/null +++ b/2016/src/day18.rs @@ -0,0 +1,89 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day18.txt"); + +pub(crate) fn day18_part1() -> usize { + let input = parse(INPUT); + safe_tile_count_of_generated_grid(&input[0], 40) +} + +pub(crate) fn day18_part2() -> usize { + let input = parse(INPUT); + safe_tile_count_of_generated_grid(&input[0], 40_0000) +} + +fn safe_tile_count_of_generated_grid(input: &str, line_count: usize) -> usize { + let mut line = parse_input(input); + let mut count = count_safe_tiles(&line); + + for _ in 1..line_count { + line = calc_next_line(line); + count += count_safe_tiles(&line); + } + + count +} + +fn parse_input(input: &str) -> Vec { + input.chars().map(|c| c == '^').collect() +} + +fn extend_with_a_safe_tile_on_each_side(line: &mut Vec) { + line.insert(0, false); + line.push(false); +} + +fn count_safe_tiles(line: &[bool]) -> usize { + line.iter().filter(|&&is_trap| !is_trap).count() +} + +fn calc_next_line(mut line: Vec) -> Vec { + extend_with_a_safe_tile_on_each_side(&mut line); + // The center tile[1] is a trap if the previous left (tile[0]) and right (tile[2) were different + line.windows(3).map(|tile| tile[0] ^ tile[2]).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_input() { + assert_eq!(vec![false, false, true, true, false], parse_input("..^^.")); + } + + #[test] + fn test_extend_with_a_safe_tile_on_each_side() { + let mut line = vec![false, false, true, true, false]; + extend_with_a_safe_tile_on_each_side(&mut line); + assert_eq!(vec![false, false, false, true, true, false, false], line); + } + + #[test] + fn test_count_safe_tiles() { + assert_eq!(3, count_safe_tiles(&[false, false, true, true, false])); + } + + #[test] + fn test_calc_next_line() { + assert_eq!( + vec![false, true, true, true, true], + calc_next_line(vec![false, false, true, true, false]) + ); + } + + #[test] + fn part1_example() { + assert_eq!(38, safe_tile_count_of_generated_grid(".^^.^.^^^^", 10)); + } + + #[test] + fn part1() { + assert_eq!(2016, day18_part1()); + } + + #[test] + fn part2() { + assert_eq!(19_998_750, day18_part2()); + } +} diff --git a/2016/src/day19.rs b/2016/src/day19.rs new file mode 100644 index 0000000..21699c3 --- /dev/null +++ b/2016/src/day19.rs @@ -0,0 +1,234 @@ +const PUZZLE_INPUT: usize = 3_004_953; + +pub(crate) fn day19_part1() -> usize { + index_of_elf_who_gets_all_the_presents_part1(PUZZLE_INPUT) +} + +pub(crate) fn day19_part2() -> usize { + index_of_elf_who_gets_all_the_presents_part2(PUZZLE_INPUT) +} + +enum Method { + #[allow(unused)] + Naive, + Fast, +} +fn index_of_elf_who_gets_all_the_presents_part1(initial_elf_count: usize) -> usize { + let method = Method::Fast; + match method { + Method::Naive => { + let mut elves: Vec<_> = (0..initial_elf_count).into_iter().collect(); + let mut turn_idx = 0; + while elves.len() > 1 { + let remove_idx = (turn_idx + 1) % elves.len(); + elves.remove(remove_idx); + if remove_idx > turn_idx { + turn_idx += 1; + } + turn_idx %= elves.len(); + } + elves[0] + 1 // convert to 1-based index + } + Method::Fast => { + // in any round, the even-numbered remaining elves get robbed. + // if the remaining count is odd, the last elf will rob the current round's first elf, + // and reduce the count to an even number. + // if the remaining count is even, the last elf is robbed + + let mut elf_count = initial_elf_count; + let mut leftmost = 1; // first using a 1-based index + let mut iterations = 0; + while elf_count > 1 { + if elf_count % 2 == 1 { + // odd -> the last elf robs the leftmost elf, so leftmost moves to the right + leftmost += 2usize.pow(iterations + 1); + } + // println!("elf count {} left {} right {}", elf_count, left, right); + elf_count /= 2; + iterations += 1; + } + leftmost + } + } +} +fn index_of_elf_who_gets_all_the_presents_part2(initial_elf_count: usize) -> usize { + let method = Method::Fast; + match method { + Method::Naive => { + // The vec contains the original 0-based index of each elf + let mut elves: Vec<_> = (0..initial_elf_count).into_iter().collect(); + let mut turn_idx = 0; + while elves.len() > 1 { + let offset = elves.len() / 2; + let remove_idx = (turn_idx + offset) % elves.len(); + elves.remove(remove_idx); + if remove_idx > turn_idx { + turn_idx += 1; + } + turn_idx %= elves.len(); + } + elves[0] + 1 // convert to 1-based index + } + Method::Fast => { + // There's a pattern to the winning elf: + // 1 elf: 1 + + // 2 elves: 1 // enumeration up to half the elf count + // 3 elves: 3 // skip 2 pattern for the next third + + // 4 elves: 1 // enumeration up to half the elf count + // 5 elves: 2 + // 6 elves: 3 + // 7 elves: 5 // skip 2 pattern for the next third + // 8 elves: 7 + // 9 elves: 9 + + // 10 to 18 elves: 1 to 9 wins // enumeration up to half the elf count + // 19 to 27 elves: 11, 13, 15, 17, 19, 21, 23, 25, 27 wins // skip 2 pattern + + // 28 to 54 elves: 1 to 27 wins // enumeration up to half the elf count + // 55 to 81 elves: 29, 31, 33, … 81 wins // skip 2 pattern + if initial_elf_count == 1 { + return 1; + } + let power = (initial_elf_count as f64).log(3.0).ceil() as u32; + // Now 3 ^ power >= initial_elf_count + // Find out if we're in the middle third (enumeration part) or upper third (skip-2 part) + let third = 3usize.pow(power - 1); + let above_third = initial_elf_count - third; + if above_third <= third { + // middle third -> enumeration pattern + above_third + } else { + // upper third -> skip 2 pattern + 2 * above_third - third // == third + 2 * (above_third - third) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example_5_elves() { + assert_eq!(3, index_of_elf_who_gets_all_the_presents_part1(5)); + } + + #[test] + fn part1_example_6_elves() { + assert_eq!(5, index_of_elf_who_gets_all_the_presents_part1(6)); + } + + #[test] + fn part1_example_7_elves() { + assert_eq!(7, index_of_elf_who_gets_all_the_presents_part1(7)); + } + + #[test] + fn part1_example_11_elves() { + assert_eq!(7, index_of_elf_who_gets_all_the_presents_part1(11)); + } + + #[test] // naive version takes 19 minutes, fast version 1 ms + fn part1() { + assert_eq!(1_815_603, day19_part1()); + } + + #[test] + fn part2_example_1_elf() { + assert_eq!(1, index_of_elf_who_gets_all_the_presents_part2(1)); + } + + #[test] + fn part2_example_2_elves() { + assert_eq!(1, index_of_elf_who_gets_all_the_presents_part2(2)); + } + #[test] + fn part2_example_3_elves() { + assert_eq!(3, index_of_elf_who_gets_all_the_presents_part2(3)); + } + + #[test] + fn part2_example_4_to_6_elves() { + assert_eq!(1, index_of_elf_who_gets_all_the_presents_part2(4)); + assert_eq!(2, index_of_elf_who_gets_all_the_presents_part2(5)); + assert_eq!(3, index_of_elf_who_gets_all_the_presents_part2(6)); + } + #[test] + fn part2_example_7_to_9_elves() { + assert_eq!(5, index_of_elf_who_gets_all_the_presents_part2(7)); + assert_eq!(7, index_of_elf_who_gets_all_the_presents_part2(8)); + assert_eq!(9, index_of_elf_who_gets_all_the_presents_part2(9)); + } + + #[test] + fn part2_example_10_to_18_elves() { + assert_eq!(1, index_of_elf_who_gets_all_the_presents_part2(10)); + assert_eq!(2, index_of_elf_who_gets_all_the_presents_part2(11)); + assert_eq!(3, index_of_elf_who_gets_all_the_presents_part2(12)); + assert_eq!(4, index_of_elf_who_gets_all_the_presents_part2(13)); + assert_eq!(5, index_of_elf_who_gets_all_the_presents_part2(14)); + assert_eq!(6, index_of_elf_who_gets_all_the_presents_part2(15)); + assert_eq!(7, index_of_elf_who_gets_all_the_presents_part2(16)); + assert_eq!(8, index_of_elf_who_gets_all_the_presents_part2(17)); + assert_eq!(9, index_of_elf_who_gets_all_the_presents_part2(18)); + } + #[test] + fn part2_example_19_to_27_elves() { + assert_eq!(11, index_of_elf_who_gets_all_the_presents_part2(19)); + assert_eq!(13, index_of_elf_who_gets_all_the_presents_part2(20)); + assert_eq!(15, index_of_elf_who_gets_all_the_presents_part2(21)); + assert_eq!(17, index_of_elf_who_gets_all_the_presents_part2(22)); + assert_eq!(19, index_of_elf_who_gets_all_the_presents_part2(23)); + assert_eq!(21, index_of_elf_who_gets_all_the_presents_part2(24)); + assert_eq!(23, index_of_elf_who_gets_all_the_presents_part2(25)); + assert_eq!(25, index_of_elf_who_gets_all_the_presents_part2(26)); + assert_eq!(27, index_of_elf_who_gets_all_the_presents_part2(27)); + } + + #[test] + fn part2_example_28_to_54_elves() { + assert_eq!(1, index_of_elf_who_gets_all_the_presents_part2(28)); + assert_eq!(2, index_of_elf_who_gets_all_the_presents_part2(29)); + assert_eq!(3, index_of_elf_who_gets_all_the_presents_part2(30)); + assert_eq!(4, index_of_elf_who_gets_all_the_presents_part2(31)); + assert_eq!(5, index_of_elf_who_gets_all_the_presents_part2(32)); + assert_eq!(6, index_of_elf_who_gets_all_the_presents_part2(33)); + assert_eq!(7, index_of_elf_who_gets_all_the_presents_part2(34)); + assert_eq!(8, index_of_elf_who_gets_all_the_presents_part2(35)); + assert_eq!(9, index_of_elf_who_gets_all_the_presents_part2(36)); + assert_eq!(10, index_of_elf_who_gets_all_the_presents_part2(37)); + assert_eq!(11, index_of_elf_who_gets_all_the_presents_part2(38)); + assert_eq!(12, index_of_elf_who_gets_all_the_presents_part2(39)); + assert_eq!(13, index_of_elf_who_gets_all_the_presents_part2(40)); + assert_eq!(14, index_of_elf_who_gets_all_the_presents_part2(41)); + assert_eq!(15, index_of_elf_who_gets_all_the_presents_part2(42)); + assert_eq!(16, index_of_elf_who_gets_all_the_presents_part2(43)); + assert_eq!(17, index_of_elf_who_gets_all_the_presents_part2(44)); + assert_eq!(18, index_of_elf_who_gets_all_the_presents_part2(45)); + assert_eq!(19, index_of_elf_who_gets_all_the_presents_part2(46)); + assert_eq!(20, index_of_elf_who_gets_all_the_presents_part2(47)); + assert_eq!(21, index_of_elf_who_gets_all_the_presents_part2(48)); + assert_eq!(22, index_of_elf_who_gets_all_the_presents_part2(49)); + assert_eq!(23, index_of_elf_who_gets_all_the_presents_part2(50)); + assert_eq!(24, index_of_elf_who_gets_all_the_presents_part2(51)); + assert_eq!(25, index_of_elf_who_gets_all_the_presents_part2(52)); + assert_eq!(26, index_of_elf_who_gets_all_the_presents_part2(53)); + assert_eq!(27, index_of_elf_who_gets_all_the_presents_part2(54)); + } + #[test] + fn part2_example_55_to_57_and_81_elves() { + assert_eq!(29, index_of_elf_who_gets_all_the_presents_part2(55)); + assert_eq!(31, index_of_elf_who_gets_all_the_presents_part2(56)); + assert_eq!(33, index_of_elf_who_gets_all_the_presents_part2(57)); + + assert_eq!(81, index_of_elf_who_gets_all_the_presents_part2(81)); + } + + #[test] // naive version takes 12 minutes, fast version 1 ms + fn part2() { + assert_eq!(1_410_630, day19_part2()); + } +} diff --git a/2016/src/day20.rs b/2016/src/day20.rs new file mode 100644 index 0000000..952ae65 --- /dev/null +++ b/2016/src/day20.rs @@ -0,0 +1,103 @@ +use crate::parse; +use std::collections::VecDeque; +use std::ops::RangeInclusive; + +const INPUT: &str = include_str!("../input/day20.txt"); + +const MAX_IP: usize = 4_294_967_295; + +pub(crate) fn day20_part1() -> usize { + let blacklist: Vec = parse_rules(parse(INPUT)); + lowest_valued_non_forbidden_ip(blacklist) +} + +pub(crate) fn day20_part2() -> usize { + let blacklist: Vec = parse_rules(parse(INPUT)); + number_of_allowed_ips(blacklist) +} + +type IpRange = RangeInclusive; +fn lowest_valued_non_forbidden_ip(blacklist: Vec) -> usize { + let mut candidates: Vec<_> = blacklist.iter().map(|r| r.end() + 1).collect(); + candidates.sort_unstable(); + for candidate in candidates { + if blacklist.iter().all(|range| !range.contains(&candidate)) { + return candidate; + } + } + unreachable!() +} + +fn parse_rules(input: Vec<&str>) -> Vec { + input + .into_iter() + .map(|s| { + let (from, to) = s.split_once('-').unwrap(); + from.parse().unwrap()..=to.parse().unwrap() + }) + .collect() +} + +fn number_of_allowed_ips(blacklist: Vec) -> usize { + MAX_IP + 1 - blocked_ip_count(blacklist) +} + +fn blocked_ip_count(blacklist: Vec) -> usize { + let non_overlapping_ranges = merge_overlapping_ranges(blacklist); + non_overlapping_ranges + .into_iter() + .map(|r| r.end() - r.start() + 1) + .sum() +} + +fn merge_overlapping_ranges(mut blacklist: Vec) -> Vec { + blacklist.sort_unstable_by_key(|r| *r.start()); + let mut blacklist: VecDeque = VecDeque::from(blacklist); + let mut merged = vec![]; + while let Some(mut range) = blacklist.pop_front() { + while let Some(pos) = blacklist + .iter() + .position(|other| other.start() <= range.end()) + { + let other = blacklist.remove(pos).unwrap(); + if other.end() > range.end() { + range = *range.start()..=*other.end(); + } + } + merged.push(range); + } + merged +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +5-8 +0-2 +4-7"; + + #[test] + fn part1_example() { + let blacklist: Vec = parse_rules(parse(EXAMPLE)); + assert_eq!(3, lowest_valued_non_forbidden_ip(blacklist)); + } + + #[test] + fn part1() { + assert_eq!(22_887_907, day20_part1()); + } + + #[test] + fn part2_example() { + let blacklist: Vec = parse_rules(parse(EXAMPLE)); + assert_eq!(MAX_IP + 1 - 8, number_of_allowed_ips(blacklist)); + } + + #[test] + fn part2() { + assert_eq!(109, day20_part2()); + } +} diff --git a/2016/src/day21.rs b/2016/src/day21.rs new file mode 100644 index 0000000..68e86d1 --- /dev/null +++ b/2016/src/day21.rs @@ -0,0 +1,183 @@ +use crate::parse; + +const INPUT: &str = include_str!("../input/day21.txt"); + +pub(crate) fn day21_part1() -> String { + let ops: Vec = parse_operations(parse(INPUT)); + scramble(&ops, "abcdefgh") +} + +pub(crate) fn day21_part2() -> String { + let ops: Vec = parse_operations(parse(INPUT)); + unscramble(&ops, "fbgdceah") +} + +#[derive(Debug, Copy, Clone)] +enum Op { + SwapPos(usize, usize), + SwapLetter(char, char), + RotateLeft(usize), + RotateRight(usize), + RotateBasedOnLetterPos(char), + ReversePositions(usize, usize), + MovePos(usize, usize), +} +impl Op { + fn apply_to(&self, chars: &mut Vec) { + match self { + Op::SwapPos(x, y) => chars.swap(*x, *y), + Op::SwapLetter(x, y) => { + let (pos_x, pos_y) = chars.find_pos_pair(x, y); + chars.swap(pos_x, pos_y); + } + Op::RotateLeft(x) => chars.rotate_left(*x), + Op::RotateRight(x) => chars.rotate_right(*x), + Op::RotateBasedOnLetterPos(x) => { + let pos = chars.find_pos(x); + let mut rot = 1 + pos + if pos >= 4 { 1 } else { 0 }; + rot %= chars.len(); + chars.rotate_right(rot); + } + Op::ReversePositions(x, y) => chars[*x..=*y].reverse(), + Op::MovePos(x, y) => { + let c = chars.remove(*x); + chars.insert(*y, c); + } + } + } + fn inverse(&self, chars: &[char]) -> Op { + match self { + Op::RotateLeft(x) => Op::RotateRight(*x), + Op::RotateRight(x) => Op::RotateLeft(*x), + Op::MovePos(x, y) => Op::MovePos(*y, *x), + Op::RotateBasedOnLetterPos(x) => match chars.find_pos(x) { + 1 => Op::RotateLeft(1), + 3 => Op::RotateLeft(2), + 5 => Op::RotateLeft(3), + 7 => Op::RotateLeft(4), + 2 => Op::RotateLeft(6 % chars.len()), + 4 => Op::RotateLeft(7 % chars.len()), + 6 => Op::RotateLeft(0), + 0 => Op::RotateLeft(1), + _ => unreachable!(), + }, + op => *op, // These other ops are their own inverse + } + } +} +impl From<&str> for Op { + fn from(s: &str) -> Self { + let p: Vec<_> = s.split_ascii_whitespace().collect(); + match (p[0], p[1]) { + ("swap", "position") => Op::SwapPos(p[2].parse().unwrap(), p[5].parse().unwrap()), + ("swap", "letter") => Op::SwapLetter(p[2].parse().unwrap(), p[5].parse().unwrap()), + ("rotate", "left") => Op::RotateLeft(p[2].parse().unwrap()), + ("rotate", "right") => Op::RotateRight(p[2].parse().unwrap()), + ("rotate", "based") => Op::RotateBasedOnLetterPos(p[6].parse().unwrap()), + ("reverse", "positions") => { + Op::ReversePositions(p[2].parse().unwrap(), p[4].parse().unwrap()) + } + ("move", "position") => Op::MovePos(p[2].parse().unwrap(), p[5].parse().unwrap()), + _ => panic!("Invalid input {}", s), + } + } +} + +fn parse_operations(input: Vec<&str>) -> Vec { + input.into_iter().map(Op::from).collect() +} + +trait FindPos { + fn find_pos(&self, wanted: &char) -> usize; + fn find_pos_pair(&self, wanted1: &char, wanted2: &char) -> (usize, usize) { + (self.find_pos(wanted1), self.find_pos(wanted2)) + } +} +impl FindPos for [char] { + fn find_pos(&self, wanted: &char) -> usize { + self.iter().position(|ch| ch == wanted).unwrap() + } +} + +fn scramble(ops: &[Op], input: &str) -> String { + let chars: Vec = input.chars().collect(); + apply_ops(ops, chars) +} +fn apply_ops(ops: &[Op], mut chars: Vec) -> String { + for op in ops { + op.apply_to(&mut chars); + } + chars.into_iter().collect() +} + +fn unscramble(ops: &[Op], input: &str) -> String { + let mut chars: Vec = input.chars().collect(); + for op in ops.iter().rev() { + op.inverse(&chars).apply_to(&mut chars); + } + chars.iter().collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE_OPS: &str = "\ +swap position 4 with position 0 +swap letter d with letter b +reverse positions 0 through 4 +rotate left 1 step +move position 1 to position 4 +move position 3 to position 0 +rotate based on position of letter b +rotate based on position of letter d"; + + #[test] + fn part1_example() { + let input = "abcde"; + let ops: Vec = parse_operations(parse(EXAMPLE_OPS)); + assert_eq!("decab", scramble(&ops, input)); + } + + #[test] + fn part1() { + assert_eq!("dgfaehcb", day21_part1()); + } + + #[test] + fn part2_example() { + let ops: Vec = parse_operations(parse(EXAMPLE_OPS)); + assert_eq!("abcde", unscramble(&ops, "decab")); + } + + #[test] + fn test_reverse_rotate_based_on_letter_pos() { + let input = "abcdefgh"; + let ops = vec![Op::RotateBasedOnLetterPos('a')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('b')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('c')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('d')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('e')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('f')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + + let ops = vec![Op::RotateBasedOnLetterPos('g')]; + assert_eq!(input, unscramble(&ops, &scramble(&ops, input))); + } + + #[test] + fn part2() { + assert_eq!("fdhgacbe", day21_part2()); + } +} diff --git a/2016/src/day22.rs b/2016/src/day22.rs new file mode 100644 index 0000000..d135339 --- /dev/null +++ b/2016/src/day22.rs @@ -0,0 +1,313 @@ +use crate::parse; +use std::fmt::{Debug, Formatter}; + +const INPUT: &str = include_str!("../input/day22.txt"); + +pub(crate) fn day22_part1() -> usize { + let lines = parse(INPUT); + let pairs = parse_pairs(lines); + let stats = pairs.into_iter().map(|n| n.stats).collect(); + viable_pair_count(stats) +} + +pub(crate) fn day22_part2() -> usize { + let lines = parse(INPUT); + let pairs = parse_pairs(lines); + count_steps_to_move_goal_data_to_origin(pairs) +} + +fn parse_pairs(lines: Vec<&str>) -> Vec { + lines.iter().skip(2).map(|&s| Pair::from(s)).collect() +} + +fn viable_pair_count(mut stats: Vec) -> usize { + let mut count = 0; + while let Some(stat) = stats.pop() { + count += stats + .iter() + .filter(|&other| stat.viably_pairs_with(other)) + .count(); + } + count +} + +fn count_steps_to_move_goal_data_to_origin(pairs: Vec) -> usize { + let mut cluster = Cluster::from(pairs); + // println!("{:?}\n", cluster); + + let mut count = 0; + let mut empty = cluster.pos_of_empty_node(); + count += cluster.move_empty_node_to_the_top(&mut empty); + // println!("{:?}\n", cluster); + count += cluster.move_empty_node_to_the_right_until_swapped_with_goal_node(&mut empty); + // println!("{:?}\n", cluster); + count += cluster.move_goal_node_left_until_it_reaches_the_origin(&mut empty); + // println!("{:?}\n", cluster); + + count +} + +type Coord = usize; +type FileSize = u16; + +#[derive(PartialEq, Clone)] +enum Node { + Goal, + Empty, + Normal, + Full, +} +impl ToString for Node { + fn to_string(&self) -> String { + match self { + Node::Goal => "G", + Node::Empty => "_", + Node::Normal => ".", + Node::Full => "#", + } + .to_string() + } +} + +#[derive(PartialEq)] +struct Cluster { + grid: Vec>, +} +impl Cluster { + fn pos_of_empty_node(&self) -> Pos { + self.grid + .iter() + .enumerate() + .filter_map(|(y, row)| { + row.iter() + .enumerate() + .find(|(_, n)| n == &&Node::Empty) + .map(|(x, _)| Pos { x, y }) + }) + .next() + .unwrap() + } + fn move_empty_node_to_the_top(&mut self, empty: &mut Pos) -> usize { + let mut count = 0; + while empty.y > 0 { + match self.grid[empty.y - 1][empty.x] { + Node::Normal => { + self.grid[empty.y][empty.x] = self.grid[empty.y - 1][empty.x].clone(); + empty.y -= 1; + self.grid[empty.y][empty.x] = Node::Empty; + count += 1; + } + Node::Goal => { + return count + 1; + } + Node::Full => { + self.grid[empty.y][empty.x] = self.grid[empty.y][empty.x - 1].clone(); + empty.x -= 1; + self.grid[empty.y][empty.x] = Node::Empty; + count += 1; + } + Node::Empty => unreachable!(), + } + } + count + } + fn move_empty_node_to_the_right_until_swapped_with_goal_node( + &mut self, + empty: &mut Pos, + ) -> usize { + let mut count = 0; + while empty.x < self.grid[0].len() - 1 { + self.grid[empty.y][empty.x] = self.grid[empty.y][empty.x + 1].clone(); + empty.x += 1; + self.grid[empty.y][empty.x] = Node::Empty; + count += 1; + } + count + } + + fn move_goal_node_left_until_it_reaches_the_origin(&mut self, empty: &mut Pos) -> usize { + let mut count = 0; + while self.grid[0][1] != Node::Empty { + count += self.move_goal_node_left_once(empty); + } + count + } + + fn move_goal_node_left_once(&mut self, empty: &mut Pos) -> usize { + self.grid[empty.y][empty.x] = self.grid[empty.y + 1][empty.x].clone(); + empty.y += 1; + + self.grid[empty.y][empty.x] = self.grid[empty.y][empty.x - 1].clone(); + empty.x -= 1; + + self.grid[empty.y][empty.x] = self.grid[empty.y][empty.x - 1].clone(); + empty.x -= 1; + + self.grid[empty.y][empty.x] = self.grid[empty.y - 1][empty.x].clone(); + empty.y -= 1; + + self.grid[empty.y][empty.x] = self.grid[empty.y][empty.x + 1].clone(); + empty.x += 1; + + self.grid[empty.y][empty.x] = Node::Empty; + + 5 + } +} +impl From> for Cluster { + fn from(pairs: Vec) -> Self { + let max = pairs.iter().map(|n| &n.pos).max().unwrap(); + + // Init grid with normal nodes and mark the goal node + let mut grid = vec![vec![Node::Normal; max.x + 1]; max.y + 1]; + grid[0][max.x] = Node::Goal; + + // Find the empty node and mark nodes that wouldn't fit into it as full + let empty_node = pairs.iter().find(|pair| pair.stats.is_empty()).unwrap(); + grid[empty_node.pos.y][empty_node.pos.x] = Node::Empty; + pairs + .iter() + .filter(|pair| pair.stats.used > empty_node.stats.available) + .for_each(|pair| grid[pair.pos.y][pair.pos.x] = Node::Full); + + Cluster { grid } + } +} +impl ToString for Cluster { + fn to_string(&self) -> String { + self.grid + .iter() + .map(|row| { + row.iter() + .map(|s| s.to_string()) + .collect::>() + .join(" ") + }) + .collect::>() + .join("\n") + } +} +impl Debug for Cluster { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +#[derive(Debug, PartialEq)] +struct Pair { + pos: Pos, + stats: Stats, +} +impl From<&str> for Pair { + fn from(s: &str) -> Self { + let (left, right) = s.split_once(|c| c == ' ').unwrap(); + Pair { + pos: Pos::from(left), + stats: Stats::from(right), + } + } +} + +#[derive(Debug, PartialEq, Clone, PartialOrd, Ord, Eq)] +struct Pos { + x: Coord, + y: Coord, +} +impl From<&str> for Pos { + fn from(s: &str) -> Self { + // Example: /dev/grid/node-x0-y0 + let (left, right) = s + .trim_start_matches("/dev/grid/node-x") + .split_once(|c| c == '-') + .unwrap(); + let x = left.parse().unwrap(); + let y = right.trim_start_matches('y').parse().unwrap(); + Pos { x, y } + } +} + +#[derive(PartialEq)] +struct Stats { + used: FileSize, + available: FileSize, +} +impl From<&str> for Stats { + fn from(s: &str) -> Self { + // Example: 92T 72T 20T 78% + let parts: Vec<_> = s.trim().split_ascii_whitespace().collect(); + let used = parts[1].trim_end_matches('T').parse().unwrap(); + let available = parts[2].trim_end_matches('T').parse().unwrap(); + Stats { used, available } + } +} +impl ToString for Stats { + fn to_string(&self) -> String { + format!("{:3}/{:3}", self.used, self.used + self.available) + } +} +impl Stats { + fn is_empty(&self) -> bool { + self.used == 0 + } + fn would_fit_in(&self, other: &Stats) -> bool { + self.used <= other.available + } + fn viably_pairs_with(&self, other: &Stats) -> bool { + !self.is_empty() && self.would_fit_in(other) + || !other.is_empty() && other.would_fit_in(self) + } + #[cfg(test)] + fn new(used: FileSize, available: FileSize) -> Self { + Stats { used, available } + } +} +impl Debug for Stats { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + #[test] + fn parse_node() { + let actual = Pair::from("/dev/grid/node-x0-y0 92T 72T 20T 78%"); + let expected = Pair { + pos: Pos { x: 0, y: 0 }, + stats: Stats::new(72, 20), + }; + assert_eq!(expected, actual); + } + + #[test] + fn part1() { + assert_eq!(937, day22_part1()); + } + + const EXAMPLE: &str = "\ +root@ebhq-gridcenter# df -h +Filesystem Size Used Avail Use% +/dev/grid/node-x0-y0 10T 8T 2T 80% +/dev/grid/node-x0-y1 11T 6T 5T 54% +/dev/grid/node-x0-y2 32T 28T 4T 87% +/dev/grid/node-x1-y0 9T 7T 2T 77% +/dev/grid/node-x1-y1 8T 0T 8T 0% +/dev/grid/node-x1-y2 11T 7T 4T 63% +/dev/grid/node-x2-y0 10T 6T 4T 60% +/dev/grid/node-x2-y1 9T 8T 1T 88% +/dev/grid/node-x2-y2 9T 6T 3T 66%"; + #[test] + fn part2_example() { + let nodes = parse_pairs(parse(EXAMPLE)); + + assert_eq!(7, count_steps_to_move_goal_data_to_origin(nodes)); + } + + #[test] + fn part2() { + assert_eq!(188, day22_part2()); + } +} diff --git a/2016/src/day23.rs b/2016/src/day23.rs new file mode 100644 index 0000000..b479720 --- /dev/null +++ b/2016/src/day23.rs @@ -0,0 +1,48 @@ +use crate::assembunny::Computer; +use crate::parse; + +const INPUT: &str = include_str!("../input/day23.txt"); + +pub(crate) fn day23_part1() -> isize { + let mut computer = Computer::from(parse(INPUT)); + computer.set_register('a', 7); + computer.run() +} + +pub(crate) fn day23_part2() -> isize { + let mut computer = Computer::from(parse(INPUT)); + computer.set_register('a', 12); + computer.run() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +cpy 2 a +tgl a +tgl a +tgl a +cpy 1 a +dec a +dec a"; + + #[test] + fn part1_example() { + let mut computer = Computer::from(parse(EXAMPLE)); + assert_eq!(3, computer.run()); + } + + #[test] + fn part1() { + assert_eq!(12_330, day23_part1()); + } + + // #[test] // Slow at 4 min 17s + #[allow(unused)] + fn part2() { + assert_eq!(479_008_890, day23_part2()); + } +} diff --git a/2016/src/day24.rs b/2016/src/day24.rs new file mode 100644 index 0000000..0df4aa8 --- /dev/null +++ b/2016/src/day24.rs @@ -0,0 +1,233 @@ +use crate::parse; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashMap, HashSet}; + +const INPUT: &str = include_str!("../input/day24.txt"); + +pub(crate) fn day24_part1() -> usize { + let mut maze = Maze::from(parse(INPUT)); + maze.len_of_shortest_path_to_reach_all_points_of_interest() +} + +pub(crate) fn day24_part2() -> usize { + let mut maze = Maze::from(parse(INPUT)); + maze.len_of_shortest_round_trip_to_reach_all_points_of_interest() +} + +type StepCount = usize; +type PointOfInterestID = u8; +// HashSet can't be used, because it doesn't implement Hash +type VisitedPOIs = usize; + +#[derive(PartialEq)] +enum Tile { + Wall, + OpenSpace, + PointOfInterest(PointOfInterestID), +} +impl From for Tile { + fn from(c: char) -> Self { + match c { + '#' => Tile::Wall, + '.' => Tile::OpenSpace, + n => Tile::PointOfInterest(n.to_digit(10).unwrap() as u8), + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct Pos { + x: usize, + y: usize, +} +impl Pos { + fn new(x: usize, y: usize) -> Self { + Pos { x, y } + } +} + +struct Maze { + grid: Vec>, +} +impl From> for Maze { + fn from(v: Vec<&str>) -> Self { + Maze { + grid: v + .into_iter() + .map(|line| line.chars().map(Tile::from).collect()) + .collect(), + } + } +} +impl Maze { + fn len_of_shortest_path_to_reach_all_points_of_interest(&mut self) -> usize { + self.len_of_shortest_path(false) + } + fn len_of_shortest_round_trip_to_reach_all_points_of_interest(&mut self) -> usize { + self.len_of_shortest_path(true) + } + fn len_of_shortest_path(&mut self, return_to_start: bool) -> usize { + let points_of_interest: Vec<_> = self.point_of_interest_positions(); + let point_of_interest_count = points_of_interest.len(); + let start_pos = Maze::starting_position(points_of_interest); + + // Keeps track of the lowest step count for each position, depending on the visited POIs + let mut visited_pois_by_step_count_per_pos: Vec>> = + vec![vec![HashMap::new(); self.grid[0].len()]; self.grid.len()]; + + let mut queue = BinaryHeap::new(); + queue.push(State { + pos: start_pos, + step_count: 0, + visited: HashSet::new(), + }); + while let Some(mut state) = queue.pop() { + // Visit POI if any + if let Tile::PointOfInterest(num) = self.grid[state.pos.y][state.pos.x] { + if state.visited.insert(num) + && state.visited.len() == point_of_interest_count + && !return_to_start + { + return state.step_count; + } + } + if state.pos == start_pos && state.visited.len() == point_of_interest_count { + return state.step_count; + } + // Abort if this position was already visited with the same POIs and fewer steps + let step_count = visited_pois_by_step_count_per_pos[state.pos.y][state.pos.x] + .entry(state.visited_pois()) + .or_insert(usize::MAX); + if &state.step_count < step_count { + *step_count = state.step_count; + } else { + continue; + } + self.neighbors_of(&state.pos) + .into_iter() + .filter(|pos| self.grid[pos.y][pos.x] != Tile::Wall) + .for_each(|pos| { + queue.push(State::new(pos, state.step_count + 1, state.visited.clone())); + }); + } + unreachable!() + } + fn point_of_interest_positions(&self) -> Vec<(Pos, PointOfInterestID)> { + self.grid + .iter() + .enumerate() + .flat_map(|(y, row)| { + row.iter().enumerate().filter_map(move |(x, tile)| { + if let Tile::PointOfInterest(num) = tile { + Some((Pos { x, y }, *num)) + } else { + None + } + }) + }) + .collect() + } + fn starting_position(points_of_interest: Vec<(Pos, PointOfInterestID)>) -> Pos { + points_of_interest + .iter() + .find(|(_, num)| num == &0) + .map(|(pos, _)| *pos) + .unwrap() + } + fn neighbors_of(&self, pos: &Pos) -> Vec { + let mut neighbors = vec![]; + if pos.x > 0 { + neighbors.push(Pos::new(pos.x - 1, pos.y)); + } + if pos.y > 0 { + neighbors.push(Pos::new(pos.x, pos.y - 1)); + } + if pos.x + 1 < self.grid[0].len() { + neighbors.push(Pos::new(pos.x + 1, pos.y)); + } + if pos.y + 1 < self.grid.len() { + neighbors.push(Pos::new(pos.x, pos.y + 1)); + } + neighbors + } +} + +#[derive(Debug, Eq, PartialEq)] +struct State { + pos: Pos, + step_count: StepCount, + visited: HashSet, +} +impl State { + fn new(pos: Pos, step_count: usize, visited: HashSet) -> Self { + State { + pos, + step_count, + visited, + } + } + fn visited_pois(&self) -> VisitedPOIs { + let mut pois: Vec<_> = self.visited.iter().collect(); + // Reverse sort so the 0 ends up at the end, and doesn't disappear + pois.sort_unstable_by(|a, b| b.cmp(a)); + pois.iter().fold(0usize, |a, b| 10usize * a + **b as usize) + } +} + +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + // Prioritize fewer steps over more visited POIs + match self.step_count.cmp(&other.step_count) { + // More visited points of interest is better + Ordering::Equal => self.visited.len().cmp(&other.visited.len()), + // Fewer steps is better + step_count => step_count.reverse(), + } + } +} +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse; + + const EXAMPLE: &str = "\ +########### +#0.1.....2# +#.#######.# +#4.......3# +###########"; + + #[test] + fn part1_example() { + let mut maze = Maze::from(parse(EXAMPLE)); + assert_eq!( + 14, + maze.len_of_shortest_path_to_reach_all_points_of_interest() + ); + } + + #[test] + fn part1() { + assert_eq!(474, day24_part1()); + } + + #[test] + fn part2_example() { + let mut maze = Maze::from(parse(EXAMPLE)); + assert_eq!( + 20, + maze.len_of_shortest_round_trip_to_reach_all_points_of_interest() + ); + } + + #[test] + fn part2() { + assert_eq!(696, day24_part2()); + } +} diff --git a/2016/src/day25.rs b/2016/src/day25.rs new file mode 100644 index 0000000..5244e08 --- /dev/null +++ b/2016/src/day25.rs @@ -0,0 +1,26 @@ +use crate::assembunny::Computer; +use crate::parse; + +const INPUT: &str = include_str!("../input/day25.txt"); + +pub(crate) fn day25_part1() -> isize { + for i in 1.. { + let mut computer = Computer::from(parse(INPUT)); + computer.set_register('a', i); + let result = computer.run(); + if result == 1 { + return i; + } + } + unreachable!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1() { + assert_eq!(175, day25_part1()); + } +} diff --git a/2016/src/main.rs b/2016/src/main.rs new file mode 100644 index 0000000..b6b85be --- /dev/null +++ b/2016/src/main.rs @@ -0,0 +1,213 @@ +mod assembunny; + +fn parse(input: &str) -> Vec<&str> { + input.trim().lines().collect() +} + +mod day01; +use crate::day01::{day01_part1, day01_part2}; +fn day01() { + assert_eq!(230, day01_part1()); + assert_eq!(154, day01_part2()); +} + +mod day02; +use crate::day02::{day02_part1, day02_part2}; +fn day02() { + assert_eq!("99332", day02_part1()); + assert_eq!("DD483", day02_part2()); +} + +mod day03; +use crate::day03::{day03_part1, day03_part2}; +fn day03() { + assert_eq!(1050, day03_part1()); + assert_eq!(1921, day03_part2()); +} + +mod day04; +use crate::day04::{day04_part1, day04_part2}; +fn day04() { + assert_eq!(158835, day04_part1()); + assert_eq!(993, day04_part2()); +} + +mod day05; +use crate::day05::{day05_part1, day05_part2}; +fn day05() { + assert_eq!("801b56a7", day05_part1()); + assert_eq!("424a0197", day05_part2()); +} + +mod day06; +use crate::day06::{day06_part1, day06_part2}; +fn day06() { + assert_eq!("qtbjqiuq", day06_part1()); + assert_eq!("akothqli", day06_part2()); +} + +mod day07; +use crate::day07::{day07_part1, day07_part2}; +fn day07() { + assert_eq!(105, day07_part1()); + assert_eq!(258, day07_part2()); +} + +mod day08; +use crate::day08::{day08_part1, day08_part2}; +fn day08() { + assert_eq!(110, day08_part1()); + assert_eq!("ZJHRKCPLYJ", day08_part2()); +} + +mod day09; +use crate::day09::{day09_part1, day09_part2}; +fn day09() { + assert_eq!(112_830, day09_part1()); + assert_eq!(10_931_789_799, day09_part2()); +} + +mod day10; +use crate::day10::{day10_part1, day10_part2}; +fn day10() { + assert_eq!(86, day10_part1()); + assert_eq!(67 * 11 * 31, day10_part2()); +} + +mod day11; +use crate::day11::{day11_part1, day11_part2}; +fn day11() { + assert_eq!(37, day11_part1()); + assert_eq!(61, day11_part2()); +} + +mod day12; +use crate::day12::{day12_part1, day12_part2}; +fn day12() { + assert_eq!(318_003, day12_part1()); + assert_eq!(9_227_657, day12_part2()); +} + +mod day13; +use crate::day13::{day13_part1, day13_part2}; +fn day13() { + assert_eq!(92, day13_part1()); + assert_eq!(124, day13_part2()); +} + +mod day14; +use crate::day14::{day14_part1, day14_part2}; +fn day14() { + assert_eq!(23_890, day14_part1()); + assert_eq!(22_696, day14_part2()); +} + +mod day15; +use crate::day15::{day15_part1, day15_part2}; +fn day15() { + assert_eq!(376_777, day15_part1()); + assert_eq!(3_903_937, day15_part2()); +} + +mod day16; +use crate::day16::{day16_part1, day16_part2}; +fn day16() { + assert_eq!("10100011010101011", day16_part1()); + assert_eq!("01010001101011001", day16_part2()); +} + +mod day17; +use crate::day17::{day17_part1, day17_part2}; +fn day17() { + assert_eq!("DDRUDLRRRD", day17_part1()); + assert_eq!(398, day17_part2()); +} + +mod day18; +use crate::day18::{day18_part1, day18_part2}; +fn day18() { + assert_eq!(2016, day18_part1()); + assert_eq!(19_998_750, day18_part2()); +} + +mod day19; +use crate::day19::{day19_part1, day19_part2}; +fn day19() { + assert_eq!(1_815_603, day19_part1()); + assert_eq!(1_410_630, day19_part2()); +} + +mod day20; +use crate::day20::{day20_part1, day20_part2}; +fn day20() { + assert_eq!(22_887_907, day20_part1()); + assert_eq!(109, day20_part2()); +} + +mod day21; +use crate::day21::{day21_part1, day21_part2}; +fn day21() { + assert_eq!("dgfaehcb", day21_part1()); + assert_eq!("fdhgacbe", day21_part2()); +} + +mod day22; +use crate::day22::{day22_part1, day22_part2}; +fn day22() { + assert_eq!(937, day22_part1()); + assert_eq!(188, day22_part2()); +} + +mod day23; +#[allow(unused)] +use crate::day23::{day23_part1, day23_part2}; +fn day23() { + assert_eq!(12_330, day23_part1()); + // assert_eq!(479_008_890, day23_part2()); // Slow at 4 min 17s +} + +mod day24; +use crate::day24::{day24_part1, day24_part2}; +fn day24() { + assert_eq!(474, day24_part1()); + assert_eq!(696, day24_part2()); +} + +mod day25; +use day25::day25_part1; +fn day25() { + assert_eq!(175, day25_part1()); +} + +#[test] +fn all() { + main() +} + +fn main() { + day01(); + day02(); + day03(); + day04(); + day05(); + day06(); + day07(); + day08(); + day09(); + day10(); + day11(); + day12(); + day13(); + day14(); + day15(); + day16(); + day17(); + day18(); + day19(); + day20(); + day21(); + day22(); + day23(); + day24(); + day25(); +}