diff options
| author | Prefetch | 2023-02-25 11:41:27 +0100 | 
|---|---|---|
| committer | Prefetch | 2023-02-25 11:41:27 +0100 | 
| commit | 3b877bf4cc667eb8bcc787d145203600a4dba2d2 (patch) | |
| tree | c1d247def29fcb58ae28e4ae4e4d73d1b4e1b27f /d04/src/main.rs | |
Initial commit
Diffstat (limited to 'd04/src/main.rs')
| -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); +    } +} | 
