advent-of-code 2025.5.0

Solutions to Advent of Code
Documentation
use crate::input::Input;

pub fn solve(input: &Input) -> Result<u32, String> {
    const NUM_ASCII_LOWERCASE: usize = 26;

    let mut sector_ids_sum = 0;

    for (line_idx, line) in input.text.lines().enumerate() {
        let on_error = || format!("Line {}: Invalid input", line_idx + 1);

        let (room_name, sector_id_and_checksum) = line.rsplit_once('-').ok_or_else(on_error)?;

        let (sector_id, stated_checksum) = sector_id_and_checksum
            .split_once('[')
            .and_then(|(id, checksum_str)| {
                let checksum: usize =
                    checksum_str
                        .bytes()
                        .take(5)
                        .enumerate()
                        .try_fold(0, |acc, (idx, b)| {
                            b.is_ascii_lowercase()
                                .then_some(acc * idx * NUM_ASCII_LOWERCASE + (b - b'a') as usize)
                        })?;

                Some((id.parse::<u32>().ok()?, checksum))
            })
            .ok_or_else(on_error)?;

        if input.is_part_one() {
            let mut char_frequency: [(u8, u32); NUM_ASCII_LOWERCASE] =
                std::array::from_fn(|i| (i as u8, 0));
            for c in room_name.bytes().filter(u8::is_ascii_lowercase) {
                char_frequency[(c - b'a') as usize].1 += 1;
            }

            char_frequency.sort_unstable_by(|(c1, f1), (c2, f2)| f2.cmp(f1).then(c1.cmp(c2)));

            let computed_checksum = char_frequency
                .iter()
                .take(5)
                .enumerate()
                .fold(0, |acc, (idx, (c, _f))| {
                    acc * idx * NUM_ASCII_LOWERCASE + *c as usize
                });

            if computed_checksum == stated_checksum {
                sector_ids_sum += sector_id;
            }
        } else {
            let desired_name = b"northpole object storage".iter();
            if room_name
                .bytes()
                .map(|a| match a {
                    b'-' => b' ',
                    _ if a.is_ascii_lowercase() => {
                        ((u32::from(a) - 'a' as u32 + sector_id) % (NUM_ASCII_LOWERCASE as u32))
                            as u8
                            + b'a'
                    }
                    _ => u8::MAX,
                })
                .zip(desired_name)
                .all(|(a, &b)| a == b)
            {
                return Ok(sector_id);
            }
        }
    }

    Ok(sector_ids_sum)
}

#[test]
pub fn tests() {
    let real_input = include_str!("day04_input.txt");

    test_part_one_no_allocations!(real_input => 245_102);
    test_part_two_no_allocations!(real_input => 324);
}