1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use utils::prelude::*;

/// Decrypting room names.
#[derive(Clone, Debug)]
pub struct Day04<'a> {
    input: Vec<(&'a [u8], u32, &'a [u8])>,
}

impl<'a> Day04<'a> {
    pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
        let mut rooms = parser::take_while1(|&x| matches!(x, b'a'..=b'z' | b'-'))
            .then(parser::u32())
            .then(
                parser::take_while1(u8::is_ascii_lowercase)
                    .with_prefix(b'[')
                    .with_suffix(b']'),
            )
            .parse_lines(input)?;

        rooms.retain(|&(name, _, checksum)| {
            let mut counts = [0; 26];
            for &c in name {
                if c.is_ascii_lowercase() {
                    counts[(c - b'a') as usize] += 1;
                }
            }

            for &c in checksum {
                // Find the index/letter with the highest count. max_by_key(...) returns the last
                // max element so use .rev() to get first instead, to break ties alphabetically.
                let (letter, _) = counts
                    .iter()
                    .enumerate()
                    .rev()
                    .max_by_key(|&(_, &c)| c)
                    .unwrap();

                if c != b'a' + letter as u8 {
                    return false;
                }
                counts[letter] = 0;
            }

            true
        });

        Ok(Self { input: rooms })
    }

    #[must_use]
    pub fn part1(&self) -> u32 {
        self.input.iter().map(|&r| r.1).sum()
    }

    #[must_use]
    pub fn part2(&self) -> u32 {
        const NAME: [u8; 25] = *b"northpole-object-storage-";

        self.input
            .iter()
            .find(|&&(name, sector_id, _)| {
                name.len() == NAME.len()
                    && name.iter().enumerate().all(|(i, &c)| {
                        if c == b'-' {
                            NAME[i] == b'-'
                        } else {
                            ((c - b'a' + (sector_id % 26) as u8) % 26 + b'a') == NAME[i]
                        }
                    })
            })
            .unwrap()
            .1
    }
}

examples!(Day04<'_> -> (u32, u32) [
    {
        input: "aaaaa-bbb-z-y-x-123[abxyz]\n\
            a-b-c-d-e-f-g-h-987[abcde]\n\
            not-a-real-room-404[oarel]\n\
            totally-real-room-200[decoy]",
        part1: 1514,
    },
]);