Skip to content

Commit 902a2d4

Browse files
committed
feat(2015): complete refactor to aoc-rust-common for all days
Completed the migration of all 2015 solutions to the shared framework. All 26 days now implement the Solution trait and are executed by the common runner. Removed the old Solvable trait from the 2015 library.
1 parent 37c06e0 commit 902a2d4

8 files changed

Lines changed: 257 additions & 867 deletions

File tree

2015/src/day21.rs

Lines changed: 55 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,97 @@
1+
use aoc_rust_common::Solution;
2+
use std::fmt::Display;
13
use lazy_static::lazy_static;
24
use regex::Regex;
3-
use std::{cmp, fmt::Debug, vec};
4-
5+
use std::cmp;
56
use iter_tools::Itertools;
67

7-
use crate::Exercise;
8+
pub struct Day21;
89

9-
struct TwentyFirstDay {
10-
exercise: Exercise,
11-
}
10+
#[derive(Debug, Eq, PartialEq)]
11+
enum ItemType { Weapon, Armor, Ring }
1212

1313
#[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,
1819
}
1920

2021
#[rustfmt::skip]
2122
fn all_items() -> Vec<Item> {
2223
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 },
3932
]
4033
}
4134

4235
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
4940
}
5041

5142
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))
6644
}
6745

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 }
7547

7648
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 } }
8450

8551
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(); }
9255
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+
)
9961
}
10062

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
11271
}
11372
}
11473

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 }
12377

124-
fn first(&self, input: &str) -> usize {
78+
fn part1(&self, input: &str) -> Box<dyn Display> {
12579
let player = Fighter::new(100, 0, 0);
12680
let boss = Fighter::parse(input);
12781
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))
13486
}
13587

136-
fn second(&self, input: &str) -> usize {
88+
fn part2(&self, input: &str) -> Box<dyn Display> {
13789
let player = Fighter::new(100, 0, 0);
13890
let boss = Fighter::parse(input);
13991
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))
17796
}
17897
}

0 commit comments

Comments
 (0)