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
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! This is my solution for [Advent of Code - Day 6 - _Lanternfish_](https://adventofcode.com/2021/day/6)
//!
//! There is always at least one puzzle each year where a naive implementation is fairly simple, but
//! part two expands the problem size so that the naive solution will take too long to run. This is
//! one of those.
//!
//! Experience from previous years allowed me to spot that and implement a more performant solution
//! to part one, [`simulate`]. This requires the population count for each day, so there is also
//! [`parse_input`] that reduces the puzzle input to this format. Part two calls [`simulate`] again,
//! but with a higher number of days.

use std::fs;

/// The entry point for running the solutions with the 'real' puzzle input.
///
/// - The puzzle input is expected to be at `<project_root>/res/day-6-input`
/// - It is expected this will be called by [`super::main()`] when the user elects to run day 6.
pub fn run() {
    let contents = fs::read_to_string("res/day-6-input").expect("Failed to read file");
    let fish_pops = parse_input(contents);

    let part_1_pop = simulate(fish_pops, 80).iter().sum::<usize>();
    println!("Population count after 80 days: {}", part_1_pop);

    let part_2_pop = simulate(fish_pops, 256).iter().sum::<usize>();
    println!("Population count after 256 days: {}", part_2_pop);
}

/// Reduces a comma-separated list of numbers representing the number of days until that fish will
/// next reproduce, into a summary array that contains the count for each day.
fn parse_input(input: String) -> [usize; 9] {
    // parse the initial input to a list of `usize`
    let fish: Vec<usize> = input
        .trim()
        .split(',')
        .flat_map(|num| num.parse::<usize>().ok())
        .collect();

    // iterate through the fish, incrementing the relevant count for each one.
    let mut fish_population = [0usize; 9usize];
    for f in fish {
        fish_population[f] = fish_population[f] + 1;
    }

    fish_population
}

/// Recursive function that iterates the population `days` times, returning the resulting
/// population summary.
pub fn simulate(fish_pops: [usize; 9], days: usize) -> [usize; 9] {
    // base case - return the current population
    if days == 0 {
        return fish_pops;
    }

    // otherwise copy each of the populations is moved one day earlier
    let mut new_pops = [0usize; 9usize];
    for i in 1..=8 {
        new_pops[i - 1] = fish_pops[i];
    }

    // Fish in the 0-day population reproduce - creating an equal number of fish that will reproduce
    // in 9 days.
    new_pops[8] = fish_pops[0];
    // These fish also reset, and will reproduce again themselves in 7 days.
    new_pops[6] = new_pops[6] + fish_pops[0];

    // Recursively call, decrementing the remaining days
    return simulate(new_pops, days - 1);
}

#[cfg(test)]
mod tests {
    use crate::day_6::{parse_input, simulate};

    #[test]
    fn can_parse() {
        assert_eq!(
            parse_input("3,4,3,1,2".to_string()),
            [0, 1, 1, 2, 1, 0, 0, 0, 0]
        );
    }

    #[test]
    fn can_simulate() {
        assert_eq!(
            simulate([0, 1, 1, 2, 1, 0, 0, 0, 0], 1),
            [1, 1, 2, 1, 0, 0, 0, 0, 0]
        );
        assert_eq!(
            simulate([0, 1, 1, 2, 1, 0, 0, 0, 0], 2),
            [1, 2, 1, 0, 0, 0, 1, 0, 1]
        );
        assert_eq!(
            simulate([0, 1, 1, 2, 1, 0, 0, 0, 0], 18)
                .iter()
                .sum::<usize>(),
            26
        );
        assert_eq!(
            simulate([0, 1, 1, 2, 1, 0, 0, 0, 0], 80)
                .iter()
                .sum::<usize>(),
            5934
        );
        assert_eq!(
            simulate([0, 1, 1, 2, 1, 0, 0, 0, 0], 256)
                .iter()
                .sum::<usize>(),
            26984457539
        );
    }
}