diff options
Diffstat (limited to 'd04/src')
-rw-r--r-- | d04/src/main.rs | 135 |
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); + } +} |