diff --git a/aoc2020/benches/bench.rs b/aoc2020/benches/bench.rs index eee904e..ce4022b 100644 --- a/aoc2020/benches/bench.rs +++ b/aoc2020/benches/bench.rs @@ -11,6 +11,7 @@ use aoc2020::day08; use aoc2020::day09; use aoc2020::day10; use aoc2020::day11; +use aoc2020::day12; fn aoc2020_all(c: &mut Criterion) { c.bench_function("day01", |b| b.iter(|| day01::run().unwrap())); @@ -24,6 +25,7 @@ fn aoc2020_all(c: &mut Criterion) { c.bench_function("day09", |b| b.iter(|| day09::run().unwrap())); c.bench_function("day10", |b| b.iter(|| day10::run().unwrap())); c.bench_function("day11", |b| b.iter(|| day11::run().unwrap())); + c.bench_function("day12", |b| b.iter(|| day12::run().unwrap())); } criterion_group! { diff --git a/aoc2020/input/day12.txt b/aoc2020/input/day12.txt new file mode 100644 index 0000000..29f07f6 --- /dev/null +++ b/aoc2020/input/day12.txt @@ -0,0 +1,789 @@ +R180 +S4 +L90 +S2 +R90 +W3 +F30 +R90 +W3 +L90 +F68 +L90 +E5 +F88 +S1 +L90 +F46 +W5 +F51 +S2 +L90 +F53 +S5 +F19 +W2 +L90 +F91 +E2 +S3 +F83 +N5 +F79 +W1 +S3 +F11 +E4 +F53 +W4 +S5 +R90 +E1 +F76 +R90 +F47 +E5 +R90 +W3 +R90 +S5 +L180 +W5 +N4 +F10 +S2 +R90 +S4 +W4 +F29 +S5 +F34 +E2 +S4 +R90 +F73 +N2 +F43 +W1 +N4 +R90 +S3 +F26 +S5 +W1 +N5 +R90 +W3 +F29 +R90 +W4 +F81 +R90 +E5 +S4 +W1 +F73 +N2 +E1 +F66 +L90 +E4 +S4 +E3 +N2 +F34 +L90 +W1 +F13 +S4 +E2 +F21 +N5 +E5 +N2 +F65 +L90 +N3 +R270 +F49 +E5 +L90 +E5 +R90 +F20 +S4 +F99 +W3 +N2 +E5 +L90 +F94 +R90 +S2 +R90 +S3 +F47 +S3 +R90 +F71 +W1 +R90 +N4 +E5 +S1 +F72 +L90 +F18 +E5 +F94 +L270 +F80 +W5 +R180 +N1 +F40 +R180 +N1 +E3 +N5 +F29 +R90 +E1 +R90 +E3 +L180 +E3 +R90 +S2 +L90 +E3 +L180 +F80 +E2 +S5 +E1 +N2 +L180 +F25 +N2 +L90 +F66 +R90 +F48 +N3 +L180 +N3 +R90 +E5 +R90 +F52 +R180 +S5 +L270 +S4 +L90 +F53 +S4 +W3 +W4 +F36 +W2 +N2 +N4 +L90 +W1 +R90 +E4 +F30 +N5 +W1 +S2 +W5 +F98 +W1 +N1 +F92 +L180 +N2 +R90 +S4 +L90 +F66 +N3 +L90 +F33 +W2 +S2 +E4 +F8 +W2 +L180 +F15 +S1 +E1 +F14 +S1 +W2 +L270 +F86 +E1 +R180 +W5 +R90 +N3 +L90 +N5 +E1 +F78 +N1 +L90 +F1 +F73 +R90 +F68 +W4 +F79 +F34 +N5 +R180 +E5 +L180 +S3 +W5 +F27 +R180 +F70 +R90 +S4 +F62 +L180 +N3 +R90 +S5 +F4 +R90 +S5 +W2 +N5 +R90 +F81 +L90 +N1 +F32 +E2 +N1 +W2 +L180 +E5 +R180 +S3 +E3 +R90 +S3 +F1 +N5 +E3 +F60 +W2 +F58 +L180 +S2 +E3 +L90 +F36 +L90 +E4 +E1 +F29 +W2 +R90 +R180 +N3 +L90 +R180 +S5 +W1 +F13 +R90 +W5 +S2 +R180 +F16 +W4 +L90 +W2 +S5 +F25 +R90 +F40 +L90 +L90 +F76 +S2 +R180 +N2 +R180 +W4 +S5 +F61 +N4 +E1 +F57 +E5 +F72 +W4 +F61 +R90 +S4 +F52 +W2 +N1 +F30 +L90 +F59 +N1 +L90 +W2 +N2 +F51 +E5 +F16 +S1 +W3 +L90 +F92 +E3 +N2 +F22 +L180 +N3 +F10 +E4 +N2 +F43 +R90 +F99 +S3 +R180 +E1 +S1 +W2 +L180 +F56 +N3 +F6 +N4 +F21 +L90 +N4 +L180 +N4 +F15 +E5 +S3 +W1 +F57 +E3 +S4 +W3 +L90 +S3 +L90 +F41 +E1 +S5 +L90 +F63 +N4 +F89 +F57 +S1 +L90 +S4 +L180 +F96 +L180 +N5 +E4 +L90 +E1 +S1 +F77 +S3 +L90 +E4 +L90 +E5 +L90 +N3 +L180 +S1 +N2 +L90 +N3 +R90 +F45 +R180 +F57 +N3 +R90 +E4 +L180 +F37 +L90 +W2 +R90 +S1 +R90 +N3 +F77 +W5 +F80 +S1 +S5 +R90 +N5 +E5 +S3 +L270 +F15 +L180 +W5 +F48 +E1 +L180 +S4 +E1 +S4 +W5 +F76 +S1 +E1 +S1 +F55 +L90 +F70 +R180 +R90 +E4 +R90 +S3 +E2 +S3 +F84 +S4 +W3 +F100 +R90 +S1 +L90 +F37 +W4 +F3 +R180 +N3 +R90 +L90 +W2 +F28 +R270 +N5 +R180 +S2 +E4 +F54 +L90 +W5 +R90 +F79 +N3 +R90 +F34 +W1 +N3 +F76 +W3 +F2 +W1 +L90 +R90 +F72 +N1 +E3 +F79 +W1 +L90 +F8 +E4 +F87 +E2 +F4 +S3 +L90 +F100 +F94 +E4 +R270 +S1 +E2 +F70 +E4 +L90 +S2 +W5 +L90 +S5 +W2 +S2 +W5 +F19 +E2 +E1 +F62 +L90 +F55 +E3 +R180 +F74 +S3 +W2 +F66 +L180 +F32 +W5 +F66 +W1 +F48 +S3 +R90 +N5 +W2 +N2 +F18 +E3 +F41 +L90 +N4 +F56 +L90 +R90 +F42 +R90 +S3 +L90 +E2 +S2 +F7 +W3 +N5 +L180 +L90 +L90 +N5 +L90 +W4 +F82 +W2 +F68 +S1 +E3 +S2 +F87 +L90 +F93 +L90 +F16 +L90 +S4 +L90 +N3 +E2 +R90 +S2 +R180 +F94 +W1 +S3 +F68 +S2 +F70 +S3 +W4 +F29 +E5 +R180 +S2 +F63 +L180 +F65 +R90 +W1 +F64 +E1 +L180 +L90 +S5 +F65 +L90 +N3 +N4 +L270 +S1 +E2 +S3 +W3 +N1 +E5 +F16 +E2 +L90 +N1 +L90 +E1 +S4 +W2 +R180 +F27 +N1 +R180 +E2 +L90 +F20 +N1 +W3 +N1 +R90 +W5 +R90 +E3 +S2 +F48 +N1 +R180 +N3 +W1 +S4 +F92 +S3 +R90 +S5 +E1 +W5 +R90 +S2 +W1 +L90 +R180 +E3 +N3 +E1 +R180 +E4 +L90 +E5 +L90 +W3 +L270 +E1 +S5 +W5 +L180 +W3 +S2 +F16 +W5 +S4 +R90 +N3 +L90 +N4 +F43 +N2 +F83 +S2 +W5 +F58 +W4 +N5 +R180 +E3 +F81 +L90 +F61 +L90 +W4 +F41 +E3 +N2 +F74 +R90 +F6 +R90 +W1 +F18 +R90 +R90 +N2 +F87 +S4 +F16 +S3 +E3 +F67 +R90 +E2 +L90 +S4 +R90 +F5 +W1 +R90 +W4 +F54 +S1 +W1 +F100 +S5 +E2 +L90 +W3 +F61 +L180 +W2 +S3 +F68 +F35 +N3 +R180 +E4 +R270 +S5 +E4 +L180 +S3 +R90 +N5 +E4 +F60 +W4 +S1 +L90 +L180 +N5 +L180 +W3 +S1 +E3 +S1 +N2 +E4 +R180 +N4 +F7 +N3 +W4 +F89 +N4 +E3 +L90 +F97 diff --git a/aoc2020/input/day12_provided.txt b/aoc2020/input/day12_provided.txt new file mode 100644 index 0000000..d382291 --- /dev/null +++ b/aoc2020/input/day12_provided.txt @@ -0,0 +1,5 @@ +F10 +N3 +F7 +R90 +F11 diff --git a/aoc2020/src/day12.rs b/aoc2020/src/day12.rs new file mode 100644 index 0000000..d7c5855 --- /dev/null +++ b/aoc2020/src/day12.rs @@ -0,0 +1,189 @@ +use std::fmt::Write; + +use aoc::err; + +const INPUT: &str = include_str!("../input/day12.txt"); + +pub fn run() -> aoc::Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + + Ok(res) +} + +fn part1(input: &str) -> aoc::Result { + let actions: Vec = input + .lines() + .map(|line| line.parse()) + .collect::>()?; + + let mut ship = Ship::new(); + + for a in actions { + ship.process(a); + } + + Ok(ship.manhattan_distance()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Direction { + North, + South, + East, + West, +} + +#[derive(Debug, Clone, Copy)] +enum ActionKind { + Move(Direction), + + Left, + Right, + + Forward, +} + +#[derive(Debug, Clone)] +struct Action { + kind: ActionKind, + arg: u16, +} + +impl std::str::FromStr for Action { + type Err = aoc::Error; + + fn from_str(s: &str) -> aoc::Result { + debug_assert!( + s.len() >= 2, + "tried to parse action but it is too short: `{}`", + s + ); + let letter = s + .chars() + .next() + .ok_or_else(|| err!("couldn't parse action: empty string"))?; + + let kind = match letter { + 'N' => ActionKind::Move(Direction::North), + 'S' => ActionKind::Move(Direction::South), + 'E' => ActionKind::Move(Direction::East), + 'W' => ActionKind::Move(Direction::West), + + 'L' => ActionKind::Left, + 'R' => ActionKind::Right, + + 'F' => ActionKind::Forward, + + _ => return Err(err!("couldn't parse action with letter `{}`", letter)), + }; + + let arg = s[1..] + .parse() + .map_err(|e| err!("couldn't parse action arg: {}", e))?; + + Ok(Self { kind, arg }) + } +} + +#[derive(Debug, Clone)] +struct Ship { + direction: Direction, + x: i64, + y: i64, +} + +impl Ship { + fn new() -> Self { + Self { + direction: Direction::East, + x: 0, + y: 0, + } + } + + const CLOCKWISE_DIRECTIONS: &'static [Direction] = &[ + Direction::North, + Direction::East, + Direction::South, + Direction::West, + ]; + + fn manhattan_distance(&self) -> i64 { + self.x.abs() + self.y.abs() + } + + fn process(&mut self, action: Action) { + match action.kind { + ActionKind::Move(dir) => self.forward(dir, action.arg), + + ActionKind::Right => { + debug_assert!(action.arg % 90 == 0, "only right angles are supported"); + + let quarters = action.arg / 90; + + let new_dir = Self::CLOCKWISE_DIRECTIONS + .iter() + // this is litteraly a circle, reaching West and turning 90 degrees right means + // facing North again + .cycle() + // find our current ship direction + .skip_while(|dir| **dir != self.direction) + // skip as many quarters as needed + .nth(quarters as usize) + // we can unwrap safely because we called .cycle() on a non empty iterator + .unwrap(); + + self.direction = *new_dir; + } + + ActionKind::Left => { + debug_assert!(action.arg % 90 == 0, "only right angles are supported"); + + let quarters = action.arg / 90; + + let new_dir = Self::CLOCKWISE_DIRECTIONS + .iter() + // same thing as above, but reverse direction first! + .rev() + .cycle() + .skip_while(|dir| **dir != self.direction) + .nth(quarters as usize) + .unwrap(); + + self.direction = *new_dir; + } + + ActionKind::Forward => self.forward(self.direction, action.arg), + } + } + + fn forward(&mut self, direction: Direction, arg: u16) { + let arg = arg as i64; + + match direction { + Direction::North => self.y -= arg, + Direction::South => self.y += arg, + Direction::West => self.x -= arg, + Direction::East => self.x += arg, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED: &str = include_str!("../input/day12_provided.txt"); + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED).unwrap(), 25); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 1589); + } +} diff --git a/aoc2020/src/lib.rs b/aoc2020/src/lib.rs index 490893f..47112b7 100644 --- a/aoc2020/src/lib.rs +++ b/aoc2020/src/lib.rs @@ -9,3 +9,4 @@ pub mod day08; pub mod day09; pub mod day10; pub mod day11; +pub mod day12; diff --git a/aoc2020/src/main.rs b/aoc2020/src/main.rs index 3c49447..a37c9a5 100644 --- a/aoc2020/src/main.rs +++ b/aoc2020/src/main.rs @@ -12,6 +12,7 @@ use aoc2020::day08; use aoc2020::day09; use aoc2020::day10; use aoc2020::day11; +use aoc2020::day12; fn main() -> Result<()> { let days: &[DayFunc] = &[ @@ -26,6 +27,7 @@ fn main() -> Result<()> { day09::run, day10::run, day11::run, + day12::run, ]; aoc::run(days)