Skip to content

Commit

Permalink
2017 day 14
Browse files Browse the repository at this point in the history
  • Loading branch information
ictrobot committed Nov 2, 2024
1 parent ab3d2e9 commit c928d37
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 35 deletions.
39 changes: 4 additions & 35 deletions crates/year2017/src/day10.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Item = u8> + 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()
}
}

Expand Down
88 changes: 88 additions & 0 deletions crates/year2017/src/day14.rs
Original file line number Diff line number Diff line change
@@ -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<Self, InputError> {
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},
]);
45 changes: 45 additions & 0 deletions crates/year2017/src/knot_hash.rs
Original file line number Diff line number Diff line change
@@ -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<Item = u8> + 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<Item = u8> + 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<Item = u8> + 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())
}))
}
3 changes: 3 additions & 0 deletions crates/year2017/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -15,4 +17,5 @@ utils::year!(2017 => year2017, ${
11 => day11::Day11,
12 => day12::Day12,
13 => day13::Day13,
14 => day14::Day14,
});

0 comments on commit c928d37

Please sign in to comment.