|
| 1 | +use aoc_rust_common::Solution; |
| 2 | +use std::fmt::Display; |
1 | 3 | use lazy_static::lazy_static; |
2 | 4 | use regex::Regex; |
3 | | -use std::{cmp, fmt::Debug, vec}; |
4 | | - |
| 5 | +use std::cmp; |
5 | 6 | use iter_tools::Itertools; |
6 | 7 |
|
7 | | -use crate::Exercise; |
| 8 | +pub struct Day21; |
8 | 9 |
|
9 | | -struct TwentyFirstDay { |
10 | | - exercise: Exercise, |
11 | | -} |
| 10 | +#[derive(Debug, Eq, PartialEq)] |
| 11 | +enum ItemType { Weapon, Armor, Ring } |
12 | 12 |
|
13 | 13 | #[derive(Debug, Eq, PartialEq)] |
14 | | -enum ItemType { |
15 | | - Weapon, |
16 | | - Armor, |
17 | | - Ring, |
| 14 | +struct Item { |
| 15 | + t: ItemType, |
| 16 | + cost: i32, |
| 17 | + damage: i32, |
| 18 | + armor: i32, |
18 | 19 | } |
19 | 20 |
|
20 | 21 | #[rustfmt::skip] |
21 | 22 | fn all_items() -> Vec<Item> { |
22 | 23 | vec![ |
23 | | - Item { t: ItemType::Weapon, cost: 8, damage: 4, armor: 0 }, |
24 | | - Item { t: ItemType::Weapon, cost: 10, damage: 5, armor: 0 }, |
25 | | - Item { t: ItemType::Weapon, cost: 25, damage: 6, armor: 0 }, |
26 | | - Item { t: ItemType::Weapon, cost: 40, damage: 7, armor: 0 }, |
27 | | - Item { t: ItemType::Weapon, cost: 74, damage: 8, armor: 0 }, |
28 | | - Item { t: ItemType::Armor, cost: 13, damage: 0, armor: 1 }, |
29 | | - Item { t: ItemType::Armor, cost: 31, damage: 0, armor: 2 }, |
30 | | - Item { t: ItemType::Armor, cost: 53, damage: 0, armor: 3 }, |
31 | | - Item { t: ItemType::Armor, cost: 75, damage: 0, armor: 4 }, |
32 | | - Item { t: ItemType::Armor, cost: 102, damage: 0, armor: 5 }, |
33 | | - Item { t: ItemType::Ring, cost: 25, damage: 1, armor: 0 }, |
34 | | - Item { t: ItemType::Ring, cost: 50, damage: 2, armor: 0 }, |
35 | | - Item { t: ItemType::Ring, cost: 100, damage: 3, armor: 0 }, |
36 | | - Item { t: ItemType::Ring, cost: 20, damage: 0, armor: 1 }, |
37 | | - Item { t: ItemType::Ring, cost: 40, damage: 0, armor: 2 }, |
38 | | - Item { t: ItemType::Ring, cost: 80, damage: 0, armor: 3 }, |
| 24 | + Item { t: ItemType::Weapon, cost: 8, damage: 4, armor: 0 }, Item { t: ItemType::Weapon, cost: 10, damage: 5, armor: 0 }, |
| 25 | + Item { t: ItemType::Weapon, cost: 25, damage: 6, armor: 0 }, Item { t: ItemType::Weapon, cost: 40, damage: 7, armor: 0 }, |
| 26 | + Item { t: ItemType::Weapon, cost: 74, damage: 8, armor: 0 }, Item { t: ItemType::Armor, cost: 13, damage: 0, armor: 1 }, |
| 27 | + Item { t: ItemType::Armor, cost: 31, damage: 0, armor: 2 }, Item { t: ItemType::Armor, cost: 53, damage: 0, armor: 3 }, |
| 28 | + Item { t: ItemType::Armor, cost: 75, damage: 0, armor: 4 }, Item { t: ItemType::Armor, cost: 102, damage: 0, armor: 5 }, |
| 29 | + Item { t: ItemType::Ring, cost: 25, damage: 1, armor: 0 }, Item { t: ItemType::Ring, cost: 50, damage: 2, armor: 0 }, |
| 30 | + Item { t: ItemType::Ring, cost: 100, damage: 3, armor: 0 }, Item { t: ItemType::Ring, cost: 20, damage: 0, armor: 1 }, |
| 31 | + Item { t: ItemType::Ring, cost: 40, damage: 0, armor: 2 }, Item { t: ItemType::Ring, cost: 80, damage: 0, armor: 3 }, |
39 | 32 | ] |
40 | 33 | } |
41 | 34 |
|
42 | 35 | fn is_valid_item_combination(items: &[&Item]) -> bool { |
43 | | - let items_of_type = |
44 | | - |items: &[&Item], t: ItemType| items.iter().filter(|item| item.t == t).count(); |
45 | | - |
46 | | - items_of_type(items, ItemType::Weapon) == 1 |
47 | | - && items_of_type(items, ItemType::Armor) <= 1 |
48 | | - && items_of_type(items, ItemType::Ring) <= 2 |
| 36 | + let weapons = items.iter().filter(|i| i.t == ItemType::Weapon).count(); |
| 37 | + let armors = items.iter().filter(|i| i.t == ItemType::Armor).count(); |
| 38 | + let rings = items.iter().filter(|i| i.t == ItemType::Ring).count(); |
| 39 | + weapons == 1 && armors <= 1 && rings <= 2 |
49 | 40 | } |
50 | 41 |
|
51 | 42 | fn all_valid_item_combinations(items: &[Item]) -> impl Iterator<Item = Vec<&Item>> { |
52 | | - (1..items.len()) |
53 | | - .flat_map(move |length| items.iter().combinations(length)) |
54 | | - .filter(|items| is_valid_item_combination(items)) |
55 | | -} |
56 | | - |
57 | | -fn total_cost_items(items: Vec<&Item>) -> i32 { |
58 | | - items.iter().map(|item| item.cost).sum() |
59 | | -} |
60 | | - |
61 | | -#[derive(Debug, Eq, PartialEq)] |
62 | | -struct Fighter { |
63 | | - hit_points: i32, |
64 | | - damage: i32, |
65 | | - armor: i32, |
| 43 | + (1..=items.len()).flat_map(move |n| items.iter().combinations(n)).filter(|c| is_valid_item_combination(c)) |
66 | 44 | } |
67 | 45 |
|
68 | | -#[derive(Debug, Eq, PartialEq)] |
69 | | -struct Item { |
70 | | - t: ItemType, |
71 | | - cost: i32, |
72 | | - damage: i32, |
73 | | - armor: i32, |
74 | | -} |
| 46 | +struct Fighter { hit_points: i32, damage: i32, armor: i32 } |
75 | 47 |
|
76 | 48 | impl Fighter { |
77 | | - fn new(hit_points: i32, damage: i32, armor: i32) -> Self { |
78 | | - Self { |
79 | | - hit_points, |
80 | | - damage, |
81 | | - armor, |
82 | | - } |
83 | | - } |
| 49 | + fn new(hit_points: i32, damage: i32, armor: i32) -> Self { Self { hit_points, damage, armor } } |
84 | 50 |
|
85 | 51 | fn parse(specs: &str) -> Fighter { |
86 | | - lazy_static! { |
87 | | - static ref RE: Regex = Regex::new( |
88 | | - r"Hit Points: (?P<hps>\d+)\nDamage: (?P<damage>\d+)\nArmor: (?P<armor>\d+)" |
89 | | - ) |
90 | | - .unwrap(); |
91 | | - } |
| 52 | + lazy_static! { static ref RE: Regex = Regex::new(r"Hit Points: (?P<hps>\d+) |
| 53 | +Damage: (?P<damage>\d+) |
| 54 | +Armor: (?P<armor>\d+)").unwrap(); } |
92 | 55 | let captures = RE.captures(specs).unwrap(); |
93 | | - |
94 | | - let hit_points = captures.name("hps").unwrap().as_str().parse().unwrap(); |
95 | | - let damage = captures.name("damage").unwrap().as_str().parse().unwrap(); |
96 | | - let armor = captures.name("armor").unwrap().as_str().parse().unwrap(); |
97 | | - |
98 | | - Self::new(hit_points, damage, armor) |
| 56 | + Fighter::new( |
| 57 | + captures.name("hps").unwrap().as_str().parse().unwrap(), |
| 58 | + captures.name("damage").unwrap().as_str().parse().unwrap(), |
| 59 | + captures.name("armor").unwrap().as_str().parse().unwrap(), |
| 60 | + ) |
99 | 61 | } |
100 | 62 |
|
101 | | - fn simulate_fight(&self, other: &Self, equipped_items: &[&Item]) -> bool { |
102 | | - let total_damage = self.damage + equipped_items.iter().map(|&i| i.damage).sum::<i32>(); |
103 | | - let total_armor = self.armor + equipped_items.iter().map(|&i| i.armor).sum::<i32>(); |
104 | | - |
105 | | - let damage_done = cmp::max(total_damage - other.armor, 1); |
106 | | - let turns_till_kill = (other.hit_points as f32 / damage_done as f32).ceil() as i64; |
107 | | - |
108 | | - let damage_done = cmp::max(other.damage - total_armor, 1); |
109 | | - let turns_till_dead = (self.hit_points as f32 / damage_done as f32).ceil() as i64; |
110 | | - |
111 | | - turns_till_kill <= turns_till_dead |
| 63 | + fn fight(&self, other: &Self, items: &[&Item]) -> bool { |
| 64 | + let total_damage = self.damage + items.iter().map(|i| i.damage).sum::<i32>(); |
| 65 | + let total_armor = self.armor + items.iter().map(|i| i.armor).sum::<i32>(); |
| 66 | + let damage_to_other = cmp::max(1, total_damage - other.armor); |
| 67 | + let damage_to_self = cmp::max(1, other.damage - total_armor); |
| 68 | + let turns_to_win = (other.hit_points as f32 / damage_to_other as f32).ceil() as i32; |
| 69 | + let turns_to_lose = (self.hit_points as f32 / damage_to_self as f32).ceil() as i32; |
| 70 | + turns_to_win <= turns_to_lose |
112 | 71 | } |
113 | 72 | } |
114 | 73 |
|
115 | | -impl TwentyFirstDay { |
116 | | - fn solve_first(&self, input: &str) -> usize { |
117 | | - self.first(input) |
118 | | - } |
119 | | - |
120 | | - fn solve_second(&self, input: &str) -> usize { |
121 | | - self.second(input) |
122 | | - } |
| 74 | +impl Solution for Day21 { |
| 75 | + fn year(&self) -> u32 { 2015 } |
| 76 | + fn day(&self) -> u32 { 21 } |
123 | 77 |
|
124 | | - fn first(&self, input: &str) -> usize { |
| 78 | + fn part1(&self, input: &str) -> Box<dyn Display> { |
125 | 79 | let player = Fighter::new(100, 0, 0); |
126 | 80 | let boss = Fighter::parse(input); |
127 | 81 | let items = all_items(); |
128 | | - |
129 | | - all_valid_item_combinations(&items) |
130 | | - .filter(|items| player.simulate_fight(&boss, items)) |
131 | | - .map(total_cost_items) |
132 | | - .min() |
133 | | - .unwrap() as usize |
| 82 | + Box::new(all_valid_item_combinations(&items) |
| 83 | + .filter(|i| player.fight(&boss, i)) |
| 84 | + .map(|i| i.iter().map(|item| item.cost).sum::<i32>()) |
| 85 | + .min().unwrap_or(0)) |
134 | 86 | } |
135 | 87 |
|
136 | | - fn second(&self, input: &str) -> usize { |
| 88 | + fn part2(&self, input: &str) -> Box<dyn Display> { |
137 | 89 | let player = Fighter::new(100, 0, 0); |
138 | 90 | let boss = Fighter::parse(input); |
139 | 91 | let items = all_items(); |
140 | | - |
141 | | - all_valid_item_combinations(&items) |
142 | | - .filter(|items| !player.simulate_fight(&boss, items)) |
143 | | - .map(total_cost_items) |
144 | | - .max() |
145 | | - .unwrap() as usize |
146 | | - } |
147 | | -} |
148 | | - |
149 | | -#[cfg(test)] |
150 | | -mod tests { |
151 | | - use super::*; |
152 | | - const EXAMPLE: &str = include_str!("inputs/21_test.txt"); |
153 | | - const PROD: &str = include_str!("inputs/21_prod.txt"); |
154 | | - |
155 | | - #[test] |
156 | | - fn first_test() { |
157 | | - let mut first_exercise = TwentyFirstDay { |
158 | | - exercise: Exercise { |
159 | | - content: String::from(PROD), |
160 | | - example: String::from(EXAMPLE), |
161 | | - }, |
162 | | - }; |
163 | | - |
164 | | - let expected_example = 78; |
165 | | - let expected_prod = 78; |
166 | | - let result_example = first_exercise.first(PROD); |
167 | | - let result_prod = first_exercise.first(PROD); |
168 | | - assert_eq!(expected_example, result_example); |
169 | | - assert_eq!(expected_prod, result_prod); |
170 | | - |
171 | | - let expected_example = 148; |
172 | | - let expected_prod = 148; |
173 | | - let result_example = first_exercise.second(PROD); |
174 | | - let result_prod = first_exercise.second(PROD); |
175 | | - assert_eq!(expected_example, result_example); |
176 | | - assert_eq!(expected_prod, result_prod); |
| 92 | + Box::new(all_valid_item_combinations(&items) |
| 93 | + .filter(|i| !player.fight(&boss, i)) |
| 94 | + .map(|i| i.iter().map(|item| item.cost).sum::<i32>()) |
| 95 | + .max().unwrap_or(0)) |
177 | 96 | } |
178 | 97 | } |
0 commit comments