Skip to content

Commit 687d376

Browse files
committed
feat(20/2024): solve second part
1 parent 7c44ca0 commit 687d376

File tree

6 files changed

+182
-35
lines changed

6 files changed

+182
-35
lines changed

readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
| [Day 17: Chronospatial Computer](src/solutions/year2024/day17.rs) | - | - | - |
3131
| [Day 18: RAM Run](src/solutions/year2024/day18.rs) | ⭐⭐ | 2.487 | 204.885 |
3232
| [Day 19: Linen Layout](src/solutions/year2024/day19.rs) | ⭐⭐ | 2.923 | 22.751 |
33-
| [Day 20: Race Condition](src/solutions/year2024/day20.rs) | | 7.355 | - |
33+
| [Day 20: Race Condition](src/solutions/year2024/day20.rs) | | 7.355 | 346.481 |
3434

3535
# 2023
3636

src/solutions/year2024/day18.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl Default for Day18 {
8080
impl Day18 {
8181
fn new(grid_size: usize, memory_size: usize) -> Self {
8282
Self {
83-
surface: SurfaceRange::from_points(0, grid_size as isize, 0, grid_size as isize),
83+
surface: SurfaceRange::square(grid_size as isize),
8484
memory_size,
8585
}
8686
}

src/solutions/year2024/day20.rs

+72-32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::solutions::Solution;
2+
use crate::utils::deltoid_surface::DeltoidSurface;
23
use crate::utils::grid::Grid;
34
use crate::utils::point::Point;
45
use itertools::Itertools;
@@ -9,18 +10,46 @@ pub struct Day20;
910

1011
impl Solution for Day20 {
1112
fn part_one(&self, input: &str) -> String {
12-
self.cheats_in_range(input, 100..).to_string()
13+
self.part_one_cheats_in_range(input, 100..).to_string()
1314
}
1415

15-
fn part_two(&self, _input: &str) -> String {
16-
String::from('0')
16+
fn part_two(&self, input: &str) -> String {
17+
// extract surface trait and contains method
18+
// grid has function get every position in area
19+
// rename surface range as rectangular area
20+
//
21+
self.part_two_cheats_in_range(input, 100..).to_string()
1722
}
1823
}
1924

2025
impl Day20 {
21-
fn cheats_in_range<R>(&self, input: &str, range: R) -> usize
26+
fn part_one_cheats_in_range(&self, input: &str, range: impl RangeBounds<usize>) -> usize {
27+
let cheat_range_from_current = |current: Point| {
28+
current
29+
.adjacent_vectors()
30+
.map(|v| v.forward().position())
31+
.into_iter()
32+
};
33+
34+
self.cheats_in_range(input, range, &cheat_range_from_current)
35+
}
36+
37+
fn part_two_cheats_in_range(&self, input: &str, range: impl RangeBounds<usize>) -> usize {
38+
let cheat_range_from_current =
39+
|current: Point| DeltoidSurface::new(current, 20).points().into_iter();
40+
41+
self.cheats_in_range(input, range, &cheat_range_from_current)
42+
}
43+
44+
fn cheats_in_range<R, I>(
45+
&self,
46+
input: &str,
47+
range: R,
48+
cheat_positions: &dyn Fn(Point) -> I,
49+
) -> usize
2250
where
2351
R: RangeBounds<usize>,
52+
I: Iterator<Item = Point>,
2453
{
2554
let grid: Grid<char> = Grid::from(input);
2655
let start = grid.get_first_position(&'S').unwrap();
@@ -32,30 +61,30 @@ impl Day20 {
3261
.path()
3362
.iter()
3463
.flat_map(|(current_time, current)| {
35-
current
36-
.adjacent_vectors()
37-
.iter()
38-
.filter(|v| grid.is_for_point(&v.position(), '#'))
39-
.map(|p| p.forward())
64+
cheat_positions(*current)
4065
.filter(|v| {
41-
grid.get_for_point(&v.position())
66+
grid.get_for_point(v)
4267
.is_some_and(|element| ['.', 'E'].contains(element))
4368
})
44-
.filter_map(|v| {
45-
if let Some(time_after_cheat) =
46-
path_without_cheats.picoseconds_from(v.position())
47-
{
48-
if time_after_cheat > *current_time {
49-
return Some(time_after_cheat - current_time - 2);
50-
// why -2
69+
.filter_map(|cheat_position| {
70+
let time_after_cheat = path_without_cheats
71+
.picoseconds_from(cheat_position)
72+
.unwrap();
73+
let cheat_cost = current.manhattan_distance(&cheat_position) as usize;
74+
75+
if time_after_cheat > *current_time + cheat_cost {
76+
let time = time_after_cheat - current_time - cheat_cost;
77+
if range.contains(&time) {
78+
return Some(time);
5179
}
52-
}
5380

54-
None
81+
None
82+
} else {
83+
None
84+
}
5585
})
5686
.collect_vec()
5787
})
58-
.filter(|time| range.contains(time))
5988
.count()
6089
}
6190

@@ -126,17 +155,28 @@ mod tests {
126155
###############"#;
127156

128157
#[test]
129-
fn test_solve() {
130-
assert_eq!(14, Day20.cheats_in_range(EXAMPLE, 2..=2));
131-
assert_eq!(14, Day20.cheats_in_range(EXAMPLE, 4..=4));
132-
assert_eq!(2, Day20.cheats_in_range(EXAMPLE, 6..=6));
133-
assert_eq!(4, Day20.cheats_in_range(EXAMPLE, 8..=8));
134-
assert_eq!(2, Day20.cheats_in_range(EXAMPLE, 10..=10));
135-
assert_eq!(3, Day20.cheats_in_range(EXAMPLE, 12..=12));
136-
assert_eq!(1, Day20.cheats_in_range(EXAMPLE, 20..=20));
137-
assert_eq!(1, Day20.cheats_in_range(EXAMPLE, 36..=36));
138-
assert_eq!(1, Day20.cheats_in_range(EXAMPLE, 38..=38));
139-
assert_eq!(1, Day20.cheats_in_range(EXAMPLE, 40..=40));
140-
assert_eq!(1, Day20.cheats_in_range(EXAMPLE, 64..=64));
158+
fn part_one_cheats_in_range() {
159+
assert_eq!(14, Day20.part_one_cheats_in_range(EXAMPLE, 2..=2));
160+
assert_eq!(14, Day20.part_one_cheats_in_range(EXAMPLE, 4..=4));
161+
assert_eq!(2, Day20.part_one_cheats_in_range(EXAMPLE, 6..=6));
162+
assert_eq!(4, Day20.part_one_cheats_in_range(EXAMPLE, 8..=8));
163+
assert_eq!(2, Day20.part_one_cheats_in_range(EXAMPLE, 10..=10));
164+
assert_eq!(3, Day20.part_one_cheats_in_range(EXAMPLE, 12..=12));
165+
assert_eq!(1, Day20.part_one_cheats_in_range(EXAMPLE, 20..=20));
166+
assert_eq!(1, Day20.part_one_cheats_in_range(EXAMPLE, 36..=36));
167+
assert_eq!(1, Day20.part_one_cheats_in_range(EXAMPLE, 38..=38));
168+
assert_eq!(1, Day20.part_one_cheats_in_range(EXAMPLE, 40..=40));
169+
assert_eq!(1, Day20.part_one_cheats_in_range(EXAMPLE, 64..=64));
170+
}
171+
172+
#[test]
173+
fn part_two_cheats_in_range() {
174+
assert_eq!(32, Day20.part_two_cheats_in_range(EXAMPLE, 50..=50));
175+
assert_eq!(31, Day20.part_two_cheats_in_range(EXAMPLE, 52..=52));
176+
assert_eq!(29, Day20.part_two_cheats_in_range(EXAMPLE, 54..=54));
177+
assert_eq!(39, Day20.part_two_cheats_in_range(EXAMPLE, 56..=56));
178+
assert_eq!(25, Day20.part_two_cheats_in_range(EXAMPLE, 58..=58));
179+
assert_eq!(23, Day20.part_two_cheats_in_range(EXAMPLE, 60..=60));
180+
assert_eq!(20, Day20.part_two_cheats_in_range(EXAMPLE, 62..=62));
141181
}
142182
}

src/utils/deltoid_surface.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::utils::point::Point;
2+
use crate::utils::range::Range;
3+
use std::collections::HashMap;
4+
5+
type Ranges = HashMap<isize, Range>;
6+
7+
pub struct DeltoidSurface {
8+
#[allow(dead_code)]
9+
point: Point,
10+
#[allow(dead_code)]
11+
distance: usize,
12+
#[allow(dead_code)]
13+
ranges: Ranges,
14+
points: Vec<Point>,
15+
}
16+
17+
impl DeltoidSurface {
18+
pub fn new(point: Point, distance: usize) -> Self {
19+
let ranges = Self::build_ranges(point, distance);
20+
let points = Self::build_points(&ranges);
21+
22+
Self {
23+
point,
24+
distance,
25+
ranges,
26+
points,
27+
}
28+
}
29+
30+
pub fn points(&self) -> Vec<Point> {
31+
self.points.clone()
32+
}
33+
34+
fn build_ranges(point: Point, distance: usize) -> Ranges {
35+
let isize_distance = distance as isize;
36+
37+
let mut ranges: Ranges = Ranges::with_capacity(distance * 2 + 1);
38+
39+
for x in -isize_distance..=isize_distance {
40+
let height_diff = isize_distance - x.abs();
41+
let x = point.x + x;
42+
let y_range = Range::new(point.y - height_diff, point.y + height_diff).unwrap();
43+
44+
ranges.insert(x, y_range);
45+
}
46+
47+
ranges
48+
}
49+
50+
fn build_points(ranges: &Ranges) -> Vec<Point> {
51+
ranges
52+
.iter()
53+
.flat_map(|(x, y_range)| {
54+
y_range
55+
.iter()
56+
.map(|y| Point::new(*x, y))
57+
.collect::<Vec<Point>>()
58+
})
59+
.collect()
60+
}
61+
}
62+
63+
#[cfg(test)]
64+
mod tests {
65+
use crate::utils::deltoid_surface::DeltoidSurface;
66+
use crate::utils::grid::Grid;
67+
use crate::utils::point::Point;
68+
use crate::utils::surface_range::SurfaceRange;
69+
70+
#[test]
71+
fn deltoid_surface_visual_test() {
72+
const EXPECTED: &str = r#"........
73+
....#...
74+
...###..
75+
..#####.
76+
...###..
77+
....#...
78+
........
79+
........"#;
80+
81+
let expected_grid: Grid<char> = Grid::from(EXPECTED);
82+
let surface = DeltoidSurface::new(Point::new(4, 3), 2);
83+
84+
let mut current_grid: Grid<char> = Grid::filled(SurfaceRange::square(7), '.');
85+
current_grid.modify_many(surface.points(), '#');
86+
87+
assert_eq!(expected_grid.to_string(), current_grid.to_string());
88+
}
89+
90+
#[test]
91+
fn manhattan_distance_is_at_most_distance() {
92+
let middle = Point::new(4, 3);
93+
let distance = 3;
94+
95+
let surface = DeltoidSurface::new(middle, distance);
96+
97+
assert!(surface
98+
.points
99+
.iter()
100+
.all(|p| p.manhattan_distance(&middle) <= distance as isize));
101+
}
102+
}

src/utils/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod chain_pattern_finder;
2+
pub mod deltoid_surface;
23
pub mod direction;
34
pub mod graphs;
45
pub mod grid;

src/utils/surface_range.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ impl SurfaceRange {
2121
)
2222
}
2323

24+
pub fn square(size: isize) -> Self {
25+
Self::from_points(0, size, 0, size)
26+
}
27+
2428
pub fn x(&self) -> Range {
2529
self.x_range
2630
}
@@ -89,7 +93,7 @@ mod tests {
8993

9094
#[test]
9195
fn shrink() {
92-
let surface_range = SurfaceRange::from_points(0, 10, 0, 10);
96+
let surface_range = SurfaceRange::square(10);
9397
let shrunk = surface_range._shrink(1);
9498

9599
let expected = SurfaceRange::from_points(1, 9, 1, 9);

0 commit comments

Comments
 (0)