From 3b877bf4cc667eb8bcc787d145203600a4dba2d2 Mon Sep 17 00:00:00 2001
From: Prefetch
Date: Sat, 25 Feb 2023 11:41:27 +0100
Subject: Initial commit

---
 d04/src/main.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 d04/src/main.rs

(limited to 'd04/src')

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);
+    }
+}
-- 
cgit v1.2.3