diff --git a/crates/year2017/src/day10.rs b/crates/year2017/src/day10.rs index 94e2e83..4c89bbe 100644 --- a/crates/year2017/src/day10.rs +++ b/crates/year2017/src/day10.rs @@ -1,5 +1,4 @@ -use std::array; -use utils::md5; +use crate::knot_hash::{knot_hash_hex, knot_rounds}; use utils::prelude::*; /// Implementing a custom hash function. @@ -21,46 +20,16 @@ impl<'a> Day10<'a> { .parse_all(self.input) .expect("input invalid for part 1"); - let list = Self::knot_hash(lengths.iter().copied(), 1); + let list = knot_rounds(lengths.iter().copied(), 1); list[0] as u32 * list[1] as u32 } #[must_use] pub fn part2(&self) -> String { - let lengths = self.input.bytes().chain([17, 31, 73, 47, 23]); + let hex = knot_hash_hex(self.input.bytes()); - let sparse = Self::knot_hash(lengths, 64); - - let dense: [u8; 16] = array::from_fn(|i| { - sparse[16 * i..16 * (i + 1)] - .iter() - .fold(0, |acc, x| acc ^ x) - }); - - let dense_hex = md5::to_hex(array::from_fn(|i| { - u32::from_be_bytes(dense[4 * i..4 * (i + 1)].try_into().unwrap()) - })); - - String::from_utf8(dense_hex.to_vec()).unwrap() - } - - fn knot_hash(lengths: impl Iterator + Clone, rounds: u32) -> [u8; 256] { - let mut list = array::from_fn(|i| i as u8); - let mut position = 0; - let mut skip = 0; - - for _ in 0..rounds { - for length in lengths.clone() { - list[0..length as usize].reverse(); - list.rotate_left((length as usize + skip) % 256); - position = (position + length as usize + skip) % 256; - skip += 1; - } - } - - list.rotate_right(position); - list + String::from_utf8(hex.to_vec()).unwrap() } } diff --git a/crates/year2017/src/day14.rs b/crates/year2017/src/day14.rs new file mode 100644 index 0000000..269ad33 --- /dev/null +++ b/crates/year2017/src/day14.rs @@ -0,0 +1,88 @@ +use crate::knot_hash::knot_hash; +use utils::bit::BitIterator; +use utils::prelude::*; + +/// Finding connected regions in a hash-derived grid. +/// +/// This puzzle is a combination of [`Day10`](crate::Day10), which introduced the custom knot hash +/// function used to create the grid, and [`Day12`](crate::Day12), which also involved finding +/// connected components. +#[derive(Clone, Debug)] +pub struct Day14 { + grid: [u128; 128], +} + +impl Day14 { + pub fn new(input: &str, _: InputType) -> Result { + let mut grid = [0u128; 128]; + + let mut buf = Vec::with_capacity(input.len() + 4); + buf.extend_from_slice(input.as_bytes()); + buf.push(b'-'); + + for (i, row) in grid.iter_mut().enumerate() { + buf.truncate(input.len() + 1); + if i < 10 { + buf.push(b'0' + (i as u8)); + } else if i < 100 { + buf.push(b'0' + ((i / 10) as u8)); + buf.push(b'0' + ((i % 10) as u8)); + } else { + buf.push(b'0' + ((i / 100) as u8)); + buf.push(b'0' + (((i / 10) % 10) as u8)); + buf.push(b'0' + ((i % 10) as u8)); + } + + let hash = knot_hash(buf.iter().copied()); + *row = u128::from_be_bytes(hash); + } + + Ok(Day14 { grid }) + } + + #[must_use] + pub fn part1(&self) -> u32 { + self.grid.iter().map(|x| x.count_ones()).sum() + } + + #[must_use] + pub fn part2(&self) -> u32 { + let mut regions = 0; + let mut visited = [0u128; 128]; + + for (r, &row) in self.grid.iter().enumerate() { + for (_, bit) in BitIterator::ones(row) { + if visited[r] & bit == 0 { + regions += 1; + self.visit(&mut visited, r, bit) + } + } + } + + regions + } + + fn visit(&self, visited: &mut [u128; 128], r: usize, bit: u128) { + visited[r] |= bit; + + if r > 0 && self.grid[r - 1] & bit != 0 && visited[r - 1] & bit == 0 { + self.visit(visited, r - 1, bit); + } + if r < 127 && self.grid[r + 1] & bit != 0 && visited[r + 1] & bit == 0 { + self.visit(visited, r + 1, bit); + } + + let left = bit << 1; + if left != 0 && self.grid[r] & left != 0 && visited[r] & left == 0 { + self.visit(visited, r, left); + } + let right = bit >> 1; + if right != 0 && self.grid[r] & right != 0 && visited[r] & right == 0 { + self.visit(visited, r, right); + } + } +} + +examples!(Day14 -> (u32, u32) [ + {input: "flqrgnkx", part1: 8108, part2: 1242}, +]); diff --git a/crates/year2017/src/knot_hash.rs b/crates/year2017/src/knot_hash.rs new file mode 100644 index 0000000..5089dfe --- /dev/null +++ b/crates/year2017/src/knot_hash.rs @@ -0,0 +1,45 @@ +//! Knot Hash implementation. +//! +//! See [`Day10`](crate::Day10) and [`Day14`](crate::Day14). + +use std::array; +use utils::md5; + +#[inline] +pub(crate) fn knot_rounds(lengths: impl Iterator + Clone, rounds: u32) -> [u8; 256] { + let mut list = array::from_fn(|i| i as u8); + let mut position = 0; + let mut skip = 0; + + for _ in 0..rounds { + for length in lengths.clone() { + list[0..length as usize].reverse(); + list.rotate_left((length as usize + skip) % 256); + position = (position + length as usize + skip) % 256; + skip += 1; + } + } + + list.rotate_right(position); + list +} + +#[inline] +pub(crate) fn knot_hash(lengths: impl Iterator + Clone) -> [u8; 16] { + let sparse = knot_rounds(lengths.chain([17, 31, 73, 47, 23]), 64); + + array::from_fn(|i| { + sparse[16 * i..16 * (i + 1)] + .iter() + .fold(0, |acc, x| acc ^ x) + }) +} + +#[inline] +pub(crate) fn knot_hash_hex(lengths: impl Iterator + Clone) -> [u8; 32] { + let hash = knot_hash(lengths); + + md5::to_hex(array::from_fn(|i| { + u32::from_be_bytes(hash[4 * i..4 * (i + 1)].try_into().unwrap()) + })) +} diff --git a/crates/year2017/src/lib.rs b/crates/year2017/src/lib.rs index b593cfe..fc871ed 100644 --- a/crates/year2017/src/lib.rs +++ b/crates/year2017/src/lib.rs @@ -1,6 +1,8 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))] +mod knot_hash; + utils::year!(2017 => year2017, ${ 1 => day01::Day01<'_>, 2 => day02::Day02, @@ -15,4 +17,5 @@ utils::year!(2017 => year2017, ${ 11 => day11::Day11, 12 => day12::Day12, 13 => day13::Day13, + 14 => day14::Day14, });