Advent of Code solutions in Rust

feat: day 12 of AoC 2020

+308 -2
+306
aoc_2020/src/day12.rs
··· 1 + use aoc_companion::prelude::*; 2 + use aoc_utils::linalg::Vector; 3 + use itertools::Itertools; 4 + 5 + pub(crate) struct Door { 6 + instructions: Vec<Instruction>, 7 + } 8 + 9 + impl<'input> Solution<'input> for Door { 10 + fn parse(input: &'input str) -> Result<Self, ParseError> { 11 + let instructions = input.lines().map(|line| line.parse()).try_collect()?; 12 + Ok(Door { instructions }) 13 + } 14 + 15 + fn part1(&self) -> i32 { 16 + final_state::<ShipState>(&self.instructions) 17 + .position 18 + .norm_l1() 19 + } 20 + 21 + fn part2(&self) -> i32 { 22 + final_state::<WaypointState>(&self.instructions) 23 + .position 24 + .norm_l1() 25 + } 26 + } 27 + 28 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 29 + enum Instruction { 30 + MoveBy { delta: Vector<i32, 2> }, 31 + Turn { quadrants: i32 }, 32 + MoveForward { amount: i32 }, 33 + } 34 + 35 + #[derive(Debug, thiserror::Error)] 36 + pub(crate) enum ParseError { 37 + #[error(transparent)] 38 + ParseIntError(#[from] std::num::ParseIntError), 39 + #[error("first instruction byte is multibyte")] 40 + MultibyteInstruction, 41 + #[error("invalid instruction type {0:?}")] 42 + InvalidInstructionType(char), 43 + #[error("angle of {0} degrees is not axis-aligned")] 44 + AngleNotAxisAligned(i32), 45 + } 46 + 47 + fn parse_quadrants(s: &str) -> Result<i32, ParseError> { 48 + let angle = s.parse()?; 49 + if angle % 90 == 0 { 50 + Ok(angle / 90) 51 + } else { 52 + Err(ParseError::AngleNotAxisAligned(angle)) 53 + } 54 + } 55 + 56 + impl std::str::FromStr for Instruction { 57 + type Err = ParseError; 58 + 59 + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 60 + let (c, rest) = s 61 + .split_at_checked(1) 62 + .ok_or(ParseError::MultibyteInstruction)?; 63 + Ok(match c { 64 + "E" => Instruction::MoveBy { 65 + delta: Vector([rest.parse()?, 0]), 66 + }, 67 + "N" => Instruction::MoveBy { 68 + delta: Vector([0, rest.parse()?]), 69 + }, 70 + "W" => Instruction::MoveBy { 71 + delta: Vector([-rest.parse()?, 0]), 72 + }, 73 + "S" => Instruction::MoveBy { 74 + delta: Vector([0, -rest.parse()?]), 75 + }, 76 + "L" => Instruction::Turn { 77 + quadrants: parse_quadrants(rest)?, 78 + }, 79 + "R" => Instruction::Turn { 80 + quadrants: -parse_quadrants(rest)?, 81 + }, 82 + "F" => Instruction::MoveForward { 83 + amount: rest.parse()?, 84 + }, 85 + _ => Err(ParseError::InvalidInstructionType( 86 + c.chars().next().unwrap(), 87 + ))?, 88 + }) 89 + } 90 + } 91 + 92 + trait State { 93 + fn turn(&mut self, quadrants: i32); 94 + fn move_by(&mut self, delta: Vector<i32, 2>); 95 + fn move_forward(&mut self, amount: i32); 96 + 97 + fn execute(&mut self, instruction: Instruction) { 98 + match instruction { 99 + Instruction::MoveBy { delta } => self.move_by(delta), 100 + Instruction::Turn { quadrants } => self.turn(quadrants), 101 + Instruction::MoveForward { amount } => self.move_forward(amount), 102 + } 103 + } 104 + } 105 + 106 + #[derive(Debug, Clone, PartialEq, Eq)] 107 + struct ShipState { 108 + facing: Vector<i32, 2>, 109 + position: Vector<i32, 2>, 110 + } 111 + 112 + impl Default for ShipState { 113 + fn default() -> Self { 114 + Self { 115 + facing: Vector([1, 0]), 116 + position: Vector::default(), 117 + } 118 + } 119 + } 120 + 121 + impl State for ShipState { 122 + fn turn(&mut self, quadrants: i32) { 123 + self.facing = rotated(self.facing, quadrants); 124 + } 125 + 126 + fn move_by(&mut self, delta: Vector<i32, 2>) { 127 + self.position += delta; 128 + } 129 + 130 + fn move_forward(&mut self, amount: i32) { 131 + self.move_by(self.facing * amount); 132 + } 133 + } 134 + 135 + #[derive(Debug, Clone, PartialEq, Eq)] 136 + struct WaypointState { 137 + waypoint: Vector<i32, 2>, 138 + position: Vector<i32, 2>, 139 + } 140 + 141 + impl Default for WaypointState { 142 + fn default() -> Self { 143 + Self { 144 + waypoint: Vector([10, 1]), 145 + position: Vector::default(), 146 + } 147 + } 148 + } 149 + 150 + impl State for WaypointState { 151 + fn turn(&mut self, quadrants: i32) { 152 + self.waypoint = rotated(self.waypoint, quadrants); 153 + } 154 + 155 + fn move_by(&mut self, delta: Vector<i32, 2>) { 156 + self.waypoint += delta; 157 + } 158 + 159 + fn move_forward(&mut self, amount: i32) { 160 + self.position += self.waypoint * amount; 161 + } 162 + } 163 + 164 + fn rotated(mut v: Vector<i32, 2>, quadrants: i32) -> Vector<i32, 2> { 165 + if (quadrants + 4) % 2 != 0 { 166 + v = Vector([-v[1], v[0]]); 167 + } 168 + if (quadrants + 4) / 2 % 2 != 0 { 169 + v *= -1; 170 + } 171 + v 172 + } 173 + 174 + fn final_state<S: State + Default>(instructions: &[Instruction]) -> S { 175 + instructions 176 + .iter() 177 + .fold(S::default(), |mut state, &instruction| { 178 + state.execute(instruction); 179 + state 180 + }) 181 + } 182 + 183 + #[cfg(test)] 184 + mod tests { 185 + use itertools::assert_equal; 186 + 187 + use super::*; 188 + 189 + const EXAMPLE_INPUT: &str = "\ 190 + F10 191 + N3 192 + F7 193 + R90 194 + F11"; 195 + 196 + const EXAMPLE_INSTRUCTIONS: &[Instruction] = &[ 197 + Instruction::MoveForward { amount: 10 }, 198 + Instruction::MoveBy { 199 + delta: Vector([0, 3]), 200 + }, 201 + Instruction::MoveForward { amount: 7 }, 202 + Instruction::Turn { quadrants: -1 }, 203 + Instruction::MoveForward { amount: 11 }, 204 + ]; 205 + 206 + #[test] 207 + fn parse_example_input() { 208 + assert_eq!( 209 + EXAMPLE_INPUT 210 + .lines() 211 + .map(|line| line.parse().unwrap()) 212 + .collect::<Vec<Instruction>>(), 213 + EXAMPLE_INSTRUCTIONS 214 + ); 215 + } 216 + 217 + #[test] 218 + fn intermediate_ship_states_in_example() { 219 + assert_equal( 220 + EXAMPLE_INSTRUCTIONS 221 + .iter() 222 + .scan(ShipState::default(), |state, &instruction| { 223 + state.execute(instruction); 224 + Some(state.clone()) 225 + }), 226 + [ 227 + ShipState { 228 + facing: Vector([1, 0]), 229 + position: Vector([10, 0]), 230 + }, 231 + ShipState { 232 + facing: Vector([1, 0]), 233 + position: Vector([10, 3]), 234 + }, 235 + ShipState { 236 + facing: Vector([1, 0]), 237 + position: Vector([17, 3]), 238 + }, 239 + ShipState { 240 + facing: Vector([0, -1]), 241 + position: Vector([17, 3]), 242 + }, 243 + ShipState { 244 + facing: Vector([0, -1]), 245 + position: Vector([17, -8]), 246 + }, 247 + ], 248 + ); 249 + } 250 + 251 + #[test] 252 + fn final_ship_state_after_example_instructions() { 253 + assert_eq!( 254 + final_state::<ShipState>(EXAMPLE_INSTRUCTIONS), 255 + ShipState { 256 + facing: Vector([0, -1]), 257 + position: Vector([17, -8]) 258 + } 259 + ) 260 + } 261 + 262 + #[test] 263 + fn intermediate_waypoint_states_in_example() { 264 + assert_equal( 265 + EXAMPLE_INSTRUCTIONS 266 + .iter() 267 + .scan(WaypointState::default(), |state, &instruction| { 268 + state.execute(instruction); 269 + Some(state.clone()) 270 + }), 271 + [ 272 + WaypointState { 273 + waypoint: Vector([10, 1]), 274 + position: Vector([100, 10]), 275 + }, 276 + WaypointState { 277 + waypoint: Vector([10, 4]), 278 + position: Vector([100, 10]), 279 + }, 280 + WaypointState { 281 + waypoint: Vector([10, 4]), 282 + position: Vector([170, 38]), 283 + }, 284 + WaypointState { 285 + waypoint: Vector([4, -10]), 286 + position: Vector([170, 38]), 287 + }, 288 + WaypointState { 289 + waypoint: Vector([4, -10]), 290 + position: Vector([214, -72]), 291 + }, 292 + ], 293 + ); 294 + } 295 + 296 + #[test] 297 + fn final_waypoint_state_after_example_instructions() { 298 + assert_eq!( 299 + final_state::<WaypointState>(EXAMPLE_INSTRUCTIONS), 300 + WaypointState { 301 + waypoint: Vector([4, -10]), 302 + position: Vector([214, -72]), 303 + } 304 + ) 305 + } 306 + }
+2 -2
aoc_2020/src/main.rs
··· 11 11 mod day09; 12 12 mod day10; 13 13 mod day11; 14 - // mod day12; 14 + mod day12; 15 15 // mod day13; 16 16 // mod day14; 17 17 // mod day15; ··· 42 42 door!(2020-12-09 ~> day09), 43 43 door!(2020-12-10 ~> day10), 44 44 door!(2020-12-11 ~> day11), 45 - // door!(2020-12-12 ~> day12), 45 + door!(2020-12-12 ~> day12), 46 46 // door!(2020-12-13 ~> day13), 47 47 // door!(2020-12-14 ~> day14), 48 48 // door!(2020-12-15 ~> day15),