|
1 | 1 | use crate::solutions::Solution;
|
| 2 | +use crate::utils::grid::Grid; |
| 3 | +use crate::utils::point::Point; |
| 4 | +use itertools::Itertools; |
| 5 | +use std::collections::HashMap; |
| 6 | +use std::ops::RangeBounds; |
2 | 7 |
|
3 | 8 | pub struct Day20;
|
4 | 9 |
|
5 | 10 | impl Solution for Day20 {
|
6 |
| - fn part_one(&self, _input: &str) -> String { |
7 |
| - String::from('0') |
| 11 | + fn part_one(&self, input: &str) -> String { |
| 12 | + self.cheats_in_range(input, 100..).to_string() |
8 | 13 | }
|
9 | 14 |
|
10 | 15 | fn part_two(&self, _input: &str) -> String {
|
11 | 16 | String::from('0')
|
12 | 17 | }
|
13 | 18 | }
|
14 | 19 |
|
| 20 | +impl Day20 { |
| 21 | + fn cheats_in_range<R>(&self, input: &str, range: R) -> usize |
| 22 | + where |
| 23 | + R: RangeBounds<usize>, |
| 24 | + { |
| 25 | + let grid: Grid<char> = Grid::from(input); |
| 26 | + let start = grid.get_first_position(&'S').unwrap(); |
| 27 | + let end = grid.get_first_position(&'E').unwrap(); |
| 28 | + |
| 29 | + let path_without_cheats = self.get_path(start, end, &grid); |
| 30 | + |
| 31 | + path_without_cheats |
| 32 | + .path() |
| 33 | + .iter() |
| 34 | + .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()) |
| 40 | + .filter(|v| { |
| 41 | + grid.get_for_point(&v.position()) |
| 42 | + .is_some_and(|element| ['.', 'E'].contains(element)) |
| 43 | + }) |
| 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 |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + None |
| 55 | + }) |
| 56 | + .collect_vec() |
| 57 | + }) |
| 58 | + .filter(|time| range.contains(time)) |
| 59 | + .count() |
| 60 | + } |
| 61 | + |
| 62 | + fn get_path(&self, start: Point, end: Point, grid: &Grid<char>) -> PathWithoutCheats { |
| 63 | + let mut current = start; |
| 64 | + let mut index: usize = 0; |
| 65 | + let mut path: HashMap<Point, usize> = HashMap::new(); |
| 66 | + |
| 67 | + while current != end { |
| 68 | + let tmp = current.adjacent(); |
| 69 | + let next = tmp |
| 70 | + .iter() |
| 71 | + .find(|p| { |
| 72 | + !path.contains_key(p) |
| 73 | + && grid |
| 74 | + .get_for_point(p) |
| 75 | + .is_some_and(|element| ['.', 'E'].contains(element)) |
| 76 | + }) |
| 77 | + .unwrap(); |
| 78 | + |
| 79 | + index += 1; |
| 80 | + path.insert(current, index); |
| 81 | + current = *next; |
| 82 | + } |
| 83 | + |
| 84 | + index += 1; |
| 85 | + path.insert(current, index); |
| 86 | + |
| 87 | + PathWithoutCheats { path } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +struct PathWithoutCheats { |
| 92 | + path: HashMap<Point, usize>, |
| 93 | +} |
| 94 | + |
| 95 | +impl PathWithoutCheats { |
| 96 | + fn picoseconds_from(&self, point: Point) -> Option<usize> { |
| 97 | + self.path.get(&point).copied() |
| 98 | + } |
| 99 | + |
| 100 | + fn path(&self) -> Vec<(usize, Point)> { |
| 101 | + self.path |
| 102 | + .iter() |
| 103 | + .map(|(point, time)| (*time, *point)) |
| 104 | + .collect() |
| 105 | + } |
| 106 | +} |
| 107 | + |
15 | 108 | #[cfg(test)]
|
16 | 109 | mod tests {
|
17 | 110 | use crate::solutions::year2024::day20::Day20;
|
18 |
| - use crate::solutions::Solution; |
19 | 111 |
|
20 |
| - const EXAMPLE: &str = r#""#; |
| 112 | + const EXAMPLE: &str = r#"############### |
| 113 | +#...#...#.....# |
| 114 | +#.#.#.#.#.###.# |
| 115 | +#S#...#.#.#...# |
| 116 | +#######.#.#.### |
| 117 | +#######.#.#...# |
| 118 | +#######.#.###.# |
| 119 | +###..E#...#...# |
| 120 | +###.#######.### |
| 121 | +#...###...#...# |
| 122 | +#.#####.#.###.# |
| 123 | +#.#...#.#.#...# |
| 124 | +#.#.#.#.#.#.### |
| 125 | +#...#...#...### |
| 126 | +###############"#; |
21 | 127 |
|
22 | 128 | #[test]
|
23 |
| - fn part_one_example_test() { |
24 |
| - assert_eq!("0", Day20.part_one(EXAMPLE)); |
| 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)); |
25 | 141 | }
|
26 | 142 | }
|
0 commit comments