Skip to main content

advent_of_code/year2015/
day15.rs

1use crate::input::Input;
2
3struct Ingredient {
4    capacity: i32,
5    durability: i32,
6    flavour: i32,
7    texture: i32,
8    calories: i32,
9}
10
11fn score_recipe(ingredients: &[Ingredient], teaspoons: &[i32], part2: bool) -> i32 {
12    if teaspoons.iter().sum::<i32>() != 100 {
13        return 0;
14    }
15
16    let mut capacity = 0;
17    let mut durability = 0;
18    let mut flavour = 0;
19    let mut texture = 0;
20    let mut calories = 0;
21
22    for i in 0..ingredients.len() {
23        capacity += ingredients[i].capacity * teaspoons[i];
24        durability += ingredients[i].durability * teaspoons[i];
25        flavour += ingredients[i].flavour * teaspoons[i];
26        texture += ingredients[i].texture * teaspoons[i];
27        calories += ingredients[i].calories * teaspoons[i];
28    }
29
30    if capacity <= 0 || durability <= 0 || flavour <= 0 || texture <= 0 {
31        // "If any properties had produced a negative total, it would have
32        // instead become zero, causing the whole score to multiply to zero."
33        return 0;
34    }
35
36    if part2 && calories != 500 {
37        return 0;
38    }
39
40    capacity * durability * flavour * texture
41}
42
43fn highest_score(
44    ingredients: &[Ingredient],
45    teaspoons: &mut [i32],
46    index: usize,
47    part2: bool,
48) -> i32 {
49    if index == teaspoons.len() {
50        return score_recipe(ingredients, teaspoons, part2);
51    }
52    let spoons_used_so_far = teaspoons.iter().take(index).sum::<i32>();
53    let mut max_score = 0;
54    for i in 0..=(100 - spoons_used_so_far) {
55        teaspoons[index] = i;
56        let score = highest_score(ingredients, teaspoons, index + 1, part2);
57        max_score = std::cmp::max(max_score, score);
58    }
59    max_score
60}
61
62pub fn solve(input: &Input) -> Result<i32, String> {
63    let error_mapper = |_| "Invalid number";
64
65    let mut ingredients = Vec::new();
66    for line in input.text.lines() {
67        let words = line.split(' ').collect::<Vec<_>>();
68        if words.len() != 11 || words.iter().any(|s| s.is_empty()) {
69            return Err("Invalid line not consisting of 11 words".to_string());
70        }
71
72        let capacity = words[2][0..words[2].len() - 1]
73            .parse::<i32>()
74            .map_err(error_mapper)?;
75        let durability = words[4][0..words[4].len() - 1]
76            .parse::<i32>()
77            .map_err(error_mapper)?;
78        let flavour = words[6][0..words[6].len() - 1]
79            .parse::<i32>()
80            .map_err(error_mapper)?;
81        let texture = words[8][0..words[8].len() - 1]
82            .parse::<i32>()
83            .map_err(error_mapper)?;
84        let calories = words[10].parse::<i32>().map_err(error_mapper)?;
85        let ingredient = Ingredient {
86            capacity,
87            durability,
88            flavour,
89            texture,
90            calories,
91        };
92        ingredients.push(ingredient);
93    }
94
95    let mut teaspoons = vec![0; ingredients.len()];
96
97    Ok(highest_score(
98        &ingredients,
99        &mut teaspoons,
100        0,
101        input.is_part_two(),
102    ))
103}
104
105#[test]
106pub fn tests() {
107    let example = "Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8
108Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3";
109    test_part_one!(example => 62_842_880);
110    test_part_two!(example => 57_600_000);
111
112    let real_input = include_str!("day15_input.txt");
113    test_part_one!(real_input => 18_965_440);
114    test_part_two!(real_input => 15_862_900);
115}