year2015/
day15.rs

1use std::array;
2use utils::prelude::*;
3
4/// Maximizing ingredient score.
5#[derive(Clone, Debug)]
6pub struct Day15 {
7    part1: i32,
8    part2: i32,
9}
10
11impl Day15 {
12    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
13        let ingredients = parser::i32()
14            .with_prefix(": capacity ")
15            .with_prefix(parser::take_while1(u8::is_ascii_alphabetic))
16            .then(parser::i32().with_prefix(", durability "))
17            .then(parser::i32().with_prefix(", flavor "))
18            .then(parser::i32().with_prefix(", texture "))
19            .then(parser::i32().with_prefix(", calories "))
20            .map(|(a, b, c, d, e)| [a, b, c, d, e])
21            .parse_lines(input)?;
22
23        let (part1, part2) = Self::ingredients(100, [0; 5], &ingredients);
24        Ok(Self { part1, part2 })
25    }
26
27    fn ingredients(teaspoons: i32, totals: [i32; 5], ingredients: &[[i32; 5]]) -> (i32, i32) {
28        if let Ok(two_ingredients) = ingredients.try_into() {
29            return Self::two_ingredients(teaspoons, totals, two_ingredients);
30        }
31
32        let (ingredient, remaining) = ingredients.split_first().unwrap();
33        (0..=teaspoons)
34            .map(|t| {
35                Self::ingredients(
36                    teaspoons - t,
37                    array::from_fn(|i| totals[i] + t * ingredient[i]),
38                    remaining,
39                )
40            })
41            .fold((0, 0), |(a1, b1), (a2, b2)| (a1.max(a2), b1.max(b2)))
42    }
43
44    fn two_ingredients(teaspoons: i32, totals: [i32; 5], ingredients: [[i32; 5]; 2]) -> (i32, i32) {
45        // Return early if the total for any property is already equal to or less than zero and
46        // neither of the two ingredients can increase it
47        if (0..5).any(|i| totals[i] <= 0 && ingredients[0][i] <= 0 && ingredients[1][i] <= 0) {
48            return (0, 0);
49        }
50
51        (0..=teaspoons)
52            .map(|t| {
53                let totals: [i32; 5] = array::from_fn(|i| {
54                    totals[i] + (t * ingredients[0][i]) + ((teaspoons - t) * ingredients[1][i])
55                });
56
57                if totals[0] <= 0 || totals[1] <= 0 || totals[2] <= 0 || totals[3] <= 0 {
58                    (0, 0)
59                } else {
60                    let score = totals[0] * totals[1] * totals[2] * totals[3];
61                    (score, if totals[4] == 500 { score } else { 0 })
62                }
63            })
64            .fold((0, 0), |(a1, b1), (a2, b2)| (a1.max(a2), b1.max(b2)))
65    }
66
67    #[must_use]
68    pub fn part1(&self) -> i32 {
69        self.part1
70    }
71
72    #[must_use]
73    pub fn part2(&self) -> i32 {
74        self.part2
75    }
76}
77
78examples!(Day15 -> (i32, i32) [
79    {
80        input: "Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8\n\
81            Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3",
82        part1: 62842880,
83        part2: 57600000,
84    },
85]);