-
Notifications
You must be signed in to change notification settings - Fork 9
/
day04.rs
105 lines (86 loc) · 3.37 KB
/
day04.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! # Security Through Obscurity
//!
//! We can quickly eliminate decoys without needing an expensive sort by checking that the
//! frequency of checksum letters is non-increasing, letters of equal frequency are in alphabetical
//! order and that there are no intervening letters in between any two frequencies.
//!
//! In part two as the [Caesar cipher](https://en.wikipedia.org/wiki/Caesar_cipher) does
//! not change the length of words, we can also eliminate most candidates with a simple length
//! check and only decrypt a much smaller number of strings.
use crate::util::parse::*;
pub struct Room<'a> {
name: &'a str,
sector_id: u32,
}
pub fn parse(input: &str) -> Vec<Room<'_>> {
let mut valid = Vec::new();
let to_index = |b: u8| (b - b'a') as usize;
'outer: for line in input.lines() {
// The sector id and checksum are fixed size leaving whatever is left over as the name.
let size = line.len();
let name = &line[..size - 11];
let sector_id = (&line[size - 10..size - 7]).unsigned();
let checksum = line[size - 6..size - 1].as_bytes();
// Count the frequency of each digit, the frequency of each frequency `fof` and the
// highest total frequency.
let mut freq = [0; 26];
let mut fof = [0; 64];
let mut highest = 0;
for b in name.bytes() {
if b != b'-' {
let index = to_index(b);
let current = freq[index];
let next = freq[index] + 1;
freq[index] = next;
fof[current] -= 1;
fof[next] += 1;
highest = highest.max(next);
}
}
// Initial check.
if freq[to_index(checksum[0])] != highest {
continue;
}
// Check each pair making sure that the frequency is non-increasing and that there are
// no letters in between (`fof` should be zero for all intervening letters).
// If the frequency is equal then also make sure letters are in alphabetical order.
for w in checksum.windows(2) {
let end = freq[to_index(w[0])];
let start = freq[to_index(w[1])];
if start > end || (start == end && w[1] <= w[0]) {
continue 'outer;
}
if (start + 1..end).any(|i| fof[i] != 0) {
continue 'outer;
}
}
valid.push(Room { name, sector_id });
}
valid
}
pub fn part1(input: &[Room<'_>]) -> u32 {
input.iter().map(|room| room.sector_id).sum()
}
pub fn part2(input: &[Room<'_>]) -> u32 {
for &Room { name, sector_id } in input {
let bytes = name.as_bytes();
// Quickly eliminate most rooms as the length of words doesn't change.
if bytes.len() == 24 && bytes[9] == b'-' && bytes[16] == b'-' {
let mut buffer = String::with_capacity(24);
// Decrypt potential candidates.
for b in name.bytes() {
if b == b'-' {
buffer.push(' ');
} else {
let rotate = (sector_id % 26) as u8;
let decrypted = (b - b'a' + rotate) % 26 + b'a';
buffer.push(decrypted as char);
}
}
if buffer == "northpole object storage" {
return sector_id;
}
}
}
unreachable!()
}