use std::collections::HashMap; use std::fs; fn sector_if_valid(room: &str) -> Option { 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 { 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); } }