1use crate::util::parse::*;
11
12pub struct Room<'a> {
13 name: &'a str,
14 sector_id: u32,
15}
16
17pub fn parse(input: &str) -> Vec<Room<'_>> {
18 input
19 .lines()
20 .filter_map(|line| {
21 let size = line.len();
23 let name = &line[..size - 11];
24 let checksum = &line.as_bytes()[size - 6..size - 1];
25
26 let mut freq = [0; 26];
29 let mut fof = [0; 26];
30 let mut highest = 0;
31
32 for b in name.bytes() {
33 if b != b'-' {
34 let index = to_index(b);
35 let current = freq[index];
36 let next = current + 1;
37
38 freq[index] = next;
39 fof[current] -= 1;
40 fof[next] += 1;
41
42 highest = highest.max(next);
43 }
44 }
45
46 let valid = freq[to_index(checksum[0])] == highest && rules(checksum, &freq, &mut fof);
48 valid.then(|| Room { name, sector_id: line[size - 10..size - 7].unsigned() })
49 })
50 .collect()
51}
52
53pub fn part1(input: &[Room<'_>]) -> u32 {
54 input.iter().map(|room| room.sector_id).sum()
55}
56
57pub fn part2(input: &[Room<'_>]) -> u32 {
58 let target = b"northpole object storage";
59
60 for &Room { name, sector_id } in input {
61 let bytes = name.as_bytes();
62
63 if bytes.len() == target.len() && bytes[9] == b'-' && bytes[16] == b'-' {
65 let rotate = (sector_id % 26) as u8;
66 let matches = bytes.iter().zip(target).all(|(&b, &t)| {
67 t == if b == b'-' { b' ' } else { (b - b'a' + rotate) % 26 + b'a' }
68 });
69 if matches {
70 return sector_id;
71 }
72 }
73 }
74
75 unreachable!()
76}
77
78fn rules(checksum: &[u8], freq: &[usize], fof: &mut [i32]) -> bool {
82 checksum.array_windows().all(|&[a, b]| {
83 let end = freq[to_index(a)];
84 let start = freq[to_index(b)];
85 fof[end] -= 1;
86 !(start > end || (start == end && b <= a) || (start + 1..=end).any(|i| fof[i] != 0))
87 })
88}
89
90fn to_index(b: u8) -> usize {
91 usize::from(b - b'a')
92}