advent-of-code 2022.0.46

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

type Frequency = i32;

fn parse_frequency_changes(
    input_string: &str,
) -> impl Iterator<Item = Result<Frequency, String>> + Clone + '_ {
    input_string.lines().enumerate().map(|(line_index, line)| {
        line.parse::<Frequency>()
            .map_err(|error| format!("Invalid input on line {}: {}", line_index + 1, error))
    })
}

pub fn solve(input: &Input) -> Result<Frequency, String> {
    const MAX_ITERATIONS: usize = 1_000_000;
    let change_iterator = parse_frequency_changes(input.text);

    if input.is_part_one() {
        change_iterator.sum::<Result<_, _>>()
    } else {
        let mut frequency: Frequency = 0;
        let mut seen_frequencies = HashSet::from([frequency]);

        let changes: Vec<Frequency> = change_iterator.collect::<Result<_, _>>()?;
        for &change in changes.iter().cycle().take(MAX_ITERATIONS) {
            frequency = frequency.checked_add(change).ok_or("Too high frequency")?;
            if !seen_frequencies.insert(frequency) {
                return Ok(frequency);
            }
        }

        Err(format!(
            "Frequency not repeated after {MAX_ITERATIONS} iterations"
        ))
    }
}

#[test]
pub fn tests() {
    use crate::input::{test_part_one, test_part_two};

    test_part_one!("+1\n-2\n+3\n+1" => 3);
    test_part_one!("+1\n+1\n+1" => 3);
    test_part_one!("+1\n+1\n-2" => 0);
    test_part_one!("-1\n-2\n-3" => -6);

    test_part_two!("+1\n-1" => 0);
    test_part_two!("+3\n+3\n+4\n-2\n-4" => 10);
    test_part_two!("-6\n+3\n+8\n+5\n-6" => 5);
    test_part_two!("+7\n+7\n-2\n-7\n-4" => 14);

    let real_input = include_str!("day01_input.txt");
    test_part_one!(real_input => 477);
    test_part_two!(real_input => 390);
}