1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Mutex;
3use utils::md5;
4use utils::prelude::*;
5
6#[derive(Clone, Debug)]
14pub struct Day14<'a> {
15 prefix: &'a str,
16}
17
18impl<'a> Day14<'a> {
19 pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
20 Ok(Self { prefix: input })
21 }
22
23 #[must_use]
24 pub fn part1(&self) -> u32 {
25 self.find_64th_key(0)
27 }
28
29 #[must_use]
30 pub fn part2(&self) -> u32 {
31 self.find_64th_key(2016)
32 }
33
34 fn find_64th_key(&self, additional: u32) -> u32 {
35 let mutex = Mutex::new((BTreeSet::new(), BTreeMap::new(), BTreeMap::new()));
36
37 md5::find_hash_with_appended_count(self.prefix, additional, |i, [a, b, c, d]: [u32; 4]| {
38 let mut nibble_bytes = [0u8; 32];
41 nibble_bytes[0..8].copy_from_slice(&spread_nibbles(a).to_be_bytes());
42 nibble_bytes[8..16].copy_from_slice(&spread_nibbles(b).to_be_bytes());
43 nibble_bytes[16..24].copy_from_slice(&spread_nibbles(c).to_be_bytes());
44 nibble_bytes[24..32].copy_from_slice(&spread_nibbles(d).to_be_bytes());
45
46 let Some(triplet) = nibble_bytes
47 .array_windows()
48 .find(|&&[a, b, c]| a == b && a == c)
49 .map(|&[a, ..]| 1u16 << a)
50 else {
51 return false;
53 };
54
55 let quintuplet = nibble_bytes
56 .array_windows()
57 .filter(|&&[a, b, c, d, e]| a == b && a == c && a == d && a == e)
58 .fold(0, |acc, &[a, ..]| acc | (1u16 << a));
59
60 let mut guard = mutex.lock().unwrap();
61 let (keys, triplets, quintuplets) = &mut *guard;
62
63 triplets.insert(i, triplet);
64
65 if quintuplets
67 .range(i + 1..i + 1001)
68 .any(|(_, &m)| triplet & m != 0)
69 {
70 keys.insert(i);
71 }
72
73 if quintuplet != 0 {
74 quintuplets.insert(i, quintuplet);
75
76 triplets
78 .range(i.saturating_sub(1000)..i)
79 .filter(|&(_, &m)| quintuplet & m != 0)
80 .for_each(|(&k, _)| {
81 keys.insert(k);
82 });
83 }
84
85 keys.iter().nth(63).is_some_and(|&k| i > k + 1000)
90 });
91
92 let (keys, ..) = mutex.into_inner().unwrap();
93 *keys.iter().nth(63).unwrap()
94 }
95}
96
97fn spread_nibbles(n: u32) -> u64 {
98 let mut n = u64::from(n);
99 n = ((n & 0x0000_0000_FFFF_0000) << 16) | (n & 0x0000_0000_0000_FFFF);
102 n = ((n & 0x0000_FF00_0000_FF00) << 8) | (n & 0x0000_00FF_0000_00FF);
105 n = ((n & 0x00F0_00F0_00F0_00F0) << 4) | (n & 0x000F_000F_000F_000F);
108 n
111}
112
113examples!(Day14<'_> -> (u32, u32) [
114 {input: "abc", part1: 22728, part2: 22551},
115]);