-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday17.rs
145 lines (123 loc) · 3.78 KB
/
day17.rs
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::str::FromStr;
use itertools::{iterate, Itertools};
use serde_scan::scan;
use crate::utils::coordinates::dim_2::Coordinates;
pub fn solve_part_one(input: &String) -> i32 {
let target_area = input.parse::<TargetArea>().unwrap();
(0..=target_area.x_max)
.into_iter()
.cartesian_product(-1000..=1000)
.filter_map(|(dx, dy)| Probe::new(dx, dy).max_height(&target_area))
.max()
.unwrap()
}
pub fn solve_part_two(input: &String) -> usize {
let target_area = input.parse::<TargetArea>().unwrap();
(0..=target_area.x_max)
.into_iter()
.cartesian_product(-1000..=1000)
.filter_map(|(dx, dy)| Probe::new(dx, dy).max_height(&target_area))
.count()
}
struct TargetArea {
x_min: i32,
x_max: i32,
y_min: i32,
y_max: i32,
}
impl FromStr for TargetArea {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let (x_min, x_max, y_min, y_max): (i32, i32, i32, i32) =
scan!("target area: x={}..{}, y={}..{}" <- s).unwrap();
Ok(TargetArea {
x_min,
x_max,
y_min,
y_max,
})
}
}
impl TargetArea {
fn hit_by(&self, c: Coordinates) -> bool {
c.x() >= self.x_min && c.x() <= self.x_max && c.y() >= self.y_min && c.y() <= self.y_max
}
}
#[derive(Debug)]
struct Probe {
coords: Coordinates,
dx: i32,
dy: i32,
}
impl Probe {
/// Returns a new `Probe` positioned at the origin, given its initial `x-` and `y-`velocities.
fn new(dx: i32, dy: i32) -> Self {
Probe {
coords: Coordinates::at(0, 0),
dx,
dy,
}
}
/// Returns the x-component of this `Probe`'s coordinates.
fn x(&self) -> i32 {
self.coords.x()
}
/// Returns the y-component of this `Probe`'s coordinates.
fn y(&self) -> i32 {
self.coords.y()
}
/// Returns an infinite iterator of `Probe`s at each step of this `Probe`'s trajectory.
fn trajectory(self) -> impl Iterator<Item = Probe> {
iterate(self, |probe| {
let coordinates = probe.coords.translate(probe.dx, probe.dy);
let x_velocity = probe.dx - probe.dx.signum();
let y_velocity = probe.dy - 1;
Probe {
coords: coordinates,
dx: x_velocity,
dy: y_velocity,
}
})
}
/// Returns the highest `y`-position reached along this `Probe`'s trajectory if and only if
/// this `Probe` enters a given target area along said trajectory.
fn max_height(self, target: &TargetArea) -> Option<i32> {
let trajectory = self
.trajectory()
.take_while(|probe| {
probe.x() <= target.x_max
&& probe.y() >= target.y_min
&& !(probe.dx == 0 && probe.x() < target.x_min)
})
.collect_vec();
let hit_target = trajectory
.last()
.map(|probe| target.hit_by(probe.coords))
.unwrap_or(false);
return if hit_target {
trajectory.into_iter().map(|probe| probe.y()).max()
} else {
None
};
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::{solve_part_one, solve_part_two};
#[rstest]
#[case(indoc::indoc ! {"
target area: x=20..30, y=-10..-5
"}.to_string(), 45)]
fn test_part_one(#[case] input: String, #[case] expected: i32) {
assert_eq!(expected, solve_part_one(&input))
}
#[rstest]
#[case(indoc::indoc ! {"
target area: x=20..30, y=-10..-5
"}.to_string(), 112)]
fn test_part_two(#[case] input: String, #[case] expected: usize) {
assert_eq!(expected, solve_part_two(&input))
}
}