Advent of Code 2019 in Rust

main.rs 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. use std::cmp::Ordering;
  2. use std::collections::{HashMap, VecDeque};
  3. use std::error::Error;
  4. use std::fs::File;
  5. use std::io::{prelude::*, BufReader};
  6. use std::result;
  7. use num::integer::gcd;
  8. const INPUT: &str = "input/input.txt";
  9. type Result<T> = result::Result<T, Box<dyn Error>>;
  10. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
  11. struct Point {
  12. x: usize,
  13. y: usize,
  14. }
  15. #[derive(Debug, PartialEq)]
  16. struct AsteroidField {
  17. asteroids: Vec<Point>,
  18. }
  19. impl AsteroidField {
  20. fn get_lines_of_sight(&self, from_point: &Point) -> HashMap<(i32, i32), VecDeque<&Point>> {
  21. let mut lines_of_sight: HashMap<(i32, i32), VecDeque<&Point>> = HashMap::new();
  22. for asteroid in self.asteroids.iter() {
  23. if from_point != asteroid {
  24. let x_dist: i32 = asteroid.x as i32 - from_point.x as i32;
  25. let y_dist: i32 = asteroid.y as i32 - from_point.y as i32;
  26. let mut x_ratio: i32 = 0;
  27. let mut y_ratio: i32 = 0;
  28. if x_dist == 0 {
  29. if y_dist > 0 {
  30. y_ratio = 1;
  31. } else {
  32. y_ratio = -1;
  33. }
  34. } else if y_dist == 0 {
  35. if x_dist > 0 {
  36. x_ratio = 1;
  37. } else {
  38. x_ratio = -1;
  39. }
  40. } else {
  41. let gcd = gcd(x_dist, y_dist);
  42. x_ratio = x_dist / gcd;
  43. y_ratio = y_dist / gcd;
  44. }
  45. lines_of_sight
  46. .entry((x_ratio, y_ratio))
  47. .and_modify(|deque| {
  48. let mut insertion_index = None;
  49. for (index, current) in deque.iter().enumerate() {
  50. if (current.x as i32 - from_point.x as i32).abs() > x_dist.abs()
  51. && (current.y as i32 - from_point.y as i32).abs() > y_dist.abs()
  52. {
  53. insertion_index = Some(index);
  54. break;
  55. }
  56. }
  57. if let Some(index) = insertion_index {
  58. deque.insert(index, asteroid);
  59. } else {
  60. deque.push_back(asteroid);
  61. }
  62. })
  63. .or_insert_with(|| {
  64. let mut deque = VecDeque::new();
  65. deque.push_back(asteroid);
  66. deque
  67. });
  68. }
  69. }
  70. lines_of_sight
  71. }
  72. fn find_monitoring_station(&self) -> (&Point, usize) {
  73. let mut asteroid_detect_scores = HashMap::new();
  74. for asteroid in self.asteroids.iter() {
  75. let lines_of_sight = self.get_lines_of_sight(asteroid);
  76. asteroid_detect_scores.insert(asteroid, lines_of_sight.len());
  77. }
  78. asteroid_detect_scores
  79. .into_iter()
  80. .max_by_key(|score| score.1)
  81. .expect("No asteroid detect scores")
  82. }
  83. fn vaporize_asteroids(&mut self, laser_point: &Point) -> Option<&Point> {
  84. let mut vaporized_counter = 0;
  85. let mut lines_of_sight = self.get_lines_of_sight(laser_point);
  86. let mut directions: Vec<(i32, i32)> = lines_of_sight.keys().map(|key| *key).collect();
  87. directions.sort_by(|a, b| {
  88. let a_deg = (a.1 as f32).atan2(a.0 as f32);
  89. let b_deg = (b.1 as f32).atan2(b.0 as f32);
  90. a_deg.partial_cmp(&b_deg).unwrap_or(Ordering::Equal)
  91. });
  92. let up = directions
  93. .iter()
  94. .position(|&dir| dir == (0, -1))
  95. .expect("No asteroid directly up from laser");
  96. directions.rotate_left(up);
  97. for direction in directions.iter() {
  98. let in_sight = lines_of_sight.get_mut(direction);
  99. if let Some(in_sight) = in_sight {
  100. if let Some(vaporized_asteroid) = in_sight.pop_back() {
  101. vaporized_counter += 1;
  102. if vaporized_counter == 200 {
  103. return Some(vaporized_asteroid);
  104. }
  105. }
  106. }
  107. }
  108. None
  109. }
  110. }
  111. fn read_asteroid_field(filename: &str) -> Result<AsteroidField> {
  112. let file = File::open(filename)?;
  113. let reader = BufReader::new(file);
  114. let mut asteroids = vec![];
  115. for (y, line) in reader.lines().enumerate() {
  116. for (x, contents) in line?.chars().enumerate() {
  117. if contents == '#' {
  118. asteroids.push(Point { x, y });
  119. }
  120. }
  121. }
  122. Ok(AsteroidField { asteroids })
  123. }
  124. fn solve_part1() -> Result<usize> {
  125. let asteroid_field = read_asteroid_field(INPUT)?;
  126. Ok(asteroid_field.find_monitoring_station().1)
  127. }
  128. fn solve_part2() -> Result<usize> {
  129. let mut asteroid_field = read_asteroid_field(INPUT)?;
  130. let vaporized200 = asteroid_field.vaporize_asteroids(&Point { x: 22, y: 25 }).unwrap();
  131. Ok(vaporized200.x * 100 + vaporized200.y)
  132. }
  133. fn main() -> Result<()> {
  134. println!("Part 1: {}", solve_part1()?);
  135. println!("Part 2: {}", solve_part2()?);
  136. Ok(())
  137. }
  138. #[cfg(test)]
  139. mod tests {
  140. use super::*;
  141. const TEST_INPUT1: &str = "input/test1.txt";
  142. const TEST_INPUT2: &str = "input/test2.txt";
  143. const TEST_INPUT3: &str = "input/test3.txt";
  144. const TEST_INPUT4: &str = "input/test4.txt";
  145. const TEST_INPUT5: &str = "input/test5.txt";
  146. #[test]
  147. fn reads_asteroid_field() {
  148. assert_eq!(
  149. read_asteroid_field(TEST_INPUT1).unwrap(),
  150. AsteroidField {
  151. asteroids: vec![
  152. Point { x: 1, y: 0 },
  153. Point { x: 4, y: 0 },
  154. Point { x: 0, y: 2 },
  155. Point { x: 1, y: 2 },
  156. Point { x: 2, y: 2 },
  157. Point { x: 3, y: 2 },
  158. Point { x: 4, y: 2 },
  159. Point { x: 4, y: 3 },
  160. Point { x: 3, y: 4 },
  161. Point { x: 4, y: 4 },
  162. ]
  163. },
  164. )
  165. }
  166. #[test]
  167. fn finds_monitoring_stations() {
  168. for (input, monitoring_point) in [
  169. (TEST_INPUT1, Point { x: 3, y: 4 }),
  170. (TEST_INPUT2, Point { x: 5, y: 8 }),
  171. (TEST_INPUT3, Point { x: 1, y: 2 }),
  172. (TEST_INPUT4, Point { x: 6, y: 3 }),
  173. (TEST_INPUT5, Point { x: 11, y: 13 }),
  174. ]
  175. .iter()
  176. {
  177. let asteroid_field = read_asteroid_field(input).unwrap();
  178. assert_eq!(asteroid_field.find_monitoring_station().0, monitoring_point);
  179. }
  180. }
  181. }