summaryrefslogtreecommitdiff
path: root/d04/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'd04/src/main.rs')
-rw-r--r--d04/src/main.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/d04/src/main.rs b/d04/src/main.rs
new file mode 100644
index 0000000..2369431
--- /dev/null
+++ b/d04/src/main.rs
@@ -0,0 +1,135 @@
+use std::collections::HashMap;
+use std::fs;
+
+fn sector_if_valid(room: &str) -> Option<u32> {
+ let mut letters = HashMap::new();
+ let mut sector = String::new();
+ let mut checksum = "";
+
+ for (i, c) in room.chars().enumerate() {
+ if c.is_ascii_lowercase() {
+ // Keep track of each letter's frequency
+ let count = letters.entry(c).or_insert(0);
+ *count += 1;
+ } else if c.is_ascii_digit() {
+ // Store the sector ID number
+ sector.push(c);
+ } else if c == '[' {
+ // Checksum is 5 characters in square brackets
+ checksum = &room[i + 1..i + 6];
+ break;
+ }
+ }
+
+ // Check if this room's checksum is valid
+ for c in checksum.chars() {
+ // Expect the most common letters, in alphabetical order if tied:
+ // score = 100 * frequency - alphabet index - constant
+ let &expected = letters
+ .iter()
+ .max_by_key(|x| 100 * x.1 - x.0.to_digit(36).unwrap())
+ .unwrap()
+ .0;
+ letters.remove(&c);
+
+ if c != expected {
+ return None;
+ }
+ }
+
+ Some(sector.parse().unwrap())
+}
+
+fn solve_part1(lines: &Vec<&str>) -> u32 {
+ let mut total = 0;
+
+ for room in lines {
+ let sec = sector_if_valid(room);
+ if sec.is_some() {
+ total += sec.unwrap();
+ }
+ }
+
+ total
+}
+
+fn solve_part2(lines: &Vec<&str>) -> Option<u32> {
+ for line in lines {
+ let osector = sector_if_valid(line);
+ if osector.is_none() {
+ continue;
+ }
+
+ // Extract ciphertext from `line'
+ let mut ctext = String::new();
+ for c in line.chars() {
+ if c.is_ascii_digit() {
+ break;
+ }
+ ctext.push(c);
+ }
+ // Ignore last '-' before sector ID
+ ctext.pop();
+
+ // Decrypt ciphertext into plaintext
+ let mut ptext = String::new();
+ for c in ctext.chars() {
+ if c == '-' {
+ ptext.push(' ');
+ } else {
+ // Apply shift cipher (shift by sector ID)
+ let old = c.to_digit(36).unwrap() - 10;
+ let new = (old + osector.unwrap()) % 26;
+ ptext.push(char::from_digit(new + 10, 36).unwrap());
+ }
+ }
+
+ // Right name found by inspecting decrypted list by eye
+ if ptext == "northpole object storage" {
+ return osector;
+ }
+ }
+
+ None // shouldn't happen
+}
+
+fn main() {
+ // Read room list from input text file
+ let input = fs::read_to_string("input.txt").unwrap();
+ let lines = input.lines().collect();
+
+ // Part 1 gives 158835 for me
+ println!("Part 1 solution: {}", solve_part1(&lines));
+
+ // Part 2 gives 993 for me
+ println!("Part 2 solution: {}", solve_part2(&lines).unwrap());
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn part1_example1() {
+ let lines = vec!["aaaaa-bbb-z-y-x-123[abxyz]"];
+ assert_eq!(solve_part1(&lines), 123);
+ }
+
+ #[test]
+ fn part1_example2() {
+ let lines = vec!["a-b-c-d-e-f-g-h-987[abcde]"];
+ assert_eq!(solve_part1(&lines), 987);
+ }
+
+ #[test]
+ fn part1_example3() {
+ let lines = vec!["not-a-real-room-404[oarel]"];
+ assert_eq!(solve_part1(&lines), 404);
+ }
+
+ #[test]
+ fn part1_example4() {
+ let lines = vec!["totally-real-room-200[decoy]"];
+ assert_eq!(solve_part1(&lines), 0);
+ }
+}