use std::collections::{BTreeMap, BTreeSet};
use std::ops::DerefMut;
use std::sync::Mutex;
use utils::md5;
use utils::prelude::*;
#[derive(Clone, Debug)]
pub struct Day14<'a> {
prefix: &'a str,
}
impl<'a> Day14<'a> {
pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
Ok(Self { prefix: input })
}
#[must_use]
pub fn part1(&self) -> u32 {
self.find_64th_key(0)
}
#[must_use]
pub fn part2(&self) -> u32 {
self.find_64th_key(2016)
}
fn find_64th_key(&self, additional: u32) -> u32 {
let mutex = Mutex::new((BTreeSet::new(), BTreeMap::new(), BTreeMap::new()));
md5::find_hash_with_appended_count(self.prefix, additional, |i, [a, b, c, d]: [u32; 4]| {
let mut nibble_bytes = [0u8; 32];
nibble_bytes[0..8].copy_from_slice(&spread_nibbles(a).to_be_bytes());
nibble_bytes[8..16].copy_from_slice(&spread_nibbles(b).to_be_bytes());
nibble_bytes[16..24].copy_from_slice(&spread_nibbles(c).to_be_bytes());
nibble_bytes[24..32].copy_from_slice(&spread_nibbles(d).to_be_bytes());
let Some(triplet) = nibble_bytes
.windows(3)
.find(|&w| w[0] == w[1] && w[0] == w[2])
.map(|w| 1u16 << w[0])
else {
return false;
};
let quintuplet = nibble_bytes
.windows(5)
.filter(|&w| w[0] == w[1] && w[0] == w[2] && w[0] == w[3] && w[0] == w[4])
.fold(0, |acc, w| acc | 1u16 << w[0]);
let mut guard = mutex.lock().unwrap();
let (ref mut keys, ref mut triplets, ref mut quintuplets) = guard.deref_mut();
triplets.insert(i, triplet);
if quintuplets
.range(i + 1..i + 1001)
.any(|(_, &m)| triplet & m != 0)
{
keys.insert(i);
}
if quintuplet != 0 {
quintuplets.insert(i, quintuplet);
triplets
.range(i.saturating_sub(1000)..i)
.filter(|(_, &m)| quintuplet & m != 0)
.for_each(|(&k, _)| {
keys.insert(k);
});
}
keys.len() >= 64
});
let (keys, ..) = mutex.into_inner().unwrap();
*keys.iter().nth(63).unwrap()
}
}
fn spread_nibbles(n: u32) -> u64 {
let mut n = u64::from(n);
n = ((n & 0x0000_0000_FFFF_0000) << 16) | (n & 0x0000_0000_0000_FFFF);
n = ((n & 0x0000_FF00_0000_FF00) << 8) | (n & 0x0000_00FF_0000_00FF);
n = ((n & 0x00F0_00F0_00F0_00F0) << 4) | (n & 0x000F_000F_000F_000F);
n
}
examples!(Day14<'_> -> (u32, u32) [
{input: "abc", part1: 22728, part2: 22551},
]);