1use utils::geometry::Vec2;
2use utils::grid;
3use utils::number::gcd;
4use utils::prelude::*;
5
6#[derive(Clone, Debug)]
8pub struct Day10 {
9 asteroids: Vec<Vec2<i16>>,
10 station: usize,
11 part1: u32,
12}
13
14impl Day10 {
15 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
16 let (rows, cols, grid) = grid::parse(
17 input,
18 0,
19 false,
20 |b| b == b'#',
21 |b| matches!(b, b'.' | b'#'),
22 |_, _| Err("expected '.' or '#'"),
23 )?;
24 if rows > i16::MAX as usize || cols > i16::MAX as usize {
25 return Err(InputError::new(input, 0, "grid too large"));
26 }
27
28 let asteroids: Vec<Vec2<i16>> = grid
29 .iter()
30 .enumerate()
31 .filter(|&(_, &cell)| cell)
32 .map(|(index, _)| Vec2::new((index % cols) as i16, (index / cols) as i16))
33 .collect();
34 if asteroids.is_empty() {
35 return Err(InputError::new(input, 0, "expected at least one asteroid"));
36 }
37
38 let delta_rows = 2 * rows - 1;
39 let delta_cols = 2 * cols - 1;
40 let delta_index = |delta: Vec2<i16>| {
41 (delta.y as i32 + rows as i32 - 1) as usize * delta_cols
42 + (delta.x as i32 + cols as i32 - 1) as usize
43 };
44
45 let mut reduced_cache = vec![u32::MAX; delta_rows * delta_cols];
46 let mut seen = vec![0u32; reduced_cache.len()];
47 let mut visible = vec![0u32; asteroids.len()];
48
49 for i in 0..asteroids.len() {
50 for j in i + 1..asteroids.len() {
51 let delta = asteroids[j] - asteroids[i];
52 let index = delta_index(delta);
53
54 let reduced_index = if reduced_cache[index] == u32::MAX {
57 let reduced = delta / gcd(delta.x, delta.y).abs();
58 let reduced_index = delta_index(reduced);
59 reduced_cache[index] = reduced_index as u32;
60 reduced_index
61 } else {
62 reduced_cache[index] as usize
63 };
64
65 if seen[reduced_index] <= i as u32 {
72 seen[reduced_index] = i as u32 + 1;
73 visible[i] += 1;
74 visible[j] += 1;
75 }
76 }
77 }
78
79 let (best_station, part1) = visible
80 .into_iter()
81 .enumerate()
82 .max_by_key(|&(_, visible)| visible)
83 .unwrap();
84
85 Ok(Self {
86 asteroids,
87 station: best_station,
88 part1,
89 })
90 }
91
92 #[must_use]
93 pub fn part1(&self) -> u32 {
94 self.part1
95 }
96
97 #[must_use]
98 pub fn part2(&self) -> u32 {
99 struct Target {
100 dir: Vec2<i16>,
101 dir_multiple: i16,
102 pos: Vec2<i16>,
103 }
104
105 assert!(
106 self.asteroids.len() > 200,
107 "expected there to be at least 201 asteroids"
108 );
109
110 let station_position = self.asteroids[self.station];
111 let mut targets: Vec<Target> = self
112 .asteroids
113 .iter()
114 .enumerate()
115 .filter(|&(i, _)| i != self.station)
116 .map(|(_, &position)| {
117 let delta = position - station_position;
118 let multiple = gcd(delta.x, delta.y).abs();
119 Target {
120 dir: delta / multiple,
121 dir_multiple: multiple,
122 pos: position,
123 }
124 })
125 .collect();
126
127 targets.sort_unstable_by(|a, b| {
129 Self::quadrant(a.dir)
130 .cmp(&Self::quadrant(b.dir))
131 .then_with(|| {
132 let cross = i32::from(a.dir.x) * i32::from(b.dir.y)
134 - i32::from(a.dir.y) * i32::from(b.dir.x);
135 0.cmp(&cross)
136 })
137 .then(a.dir_multiple.cmp(&b.dir_multiple))
138 });
139
140 let mut target_keys = Vec::with_capacity(targets.len());
144 let mut pass = 0u32;
145 for (i, target) in targets.iter().enumerate() {
146 if i > 0 && target.dir == targets[i - 1].dir {
147 pass += 1;
148 } else {
149 pass = 0;
150 }
151 target_keys.push((pass, i));
152 }
153
154 let (_, &mut (_, index), _) = target_keys.select_nth_unstable(199);
155 let position = targets[index].pos;
156 (position.x as u32) * 100 + position.y as u32
157 }
158
159 fn quadrant(direction: Vec2<i16>) -> u8 {
160 match (direction.x, direction.y) {
161 (0.., ..0) => 0, (1.., 0..) => 1, (..1, 1..) => 2, (..0, ..1) => 3, (0, 0) => unreachable!("expected non-zero direction"),
166 }
167 }
168}
169
170examples!(Day10 -> (u32, u32) [
171 {file: "day10_example0.txt", part1: 8},
172 {file: "day10_example1.txt", part1: 33},
173 {file: "day10_example2.txt", part1: 35},
174 {file: "day10_example3.txt", part1: 41},
175 {file: "day10_example4.txt", part1: 210, part2: 802},
176]);