From 580f6259bcc4d6449f2de68ed47aa92114423b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Sat, 18 Dec 2021 18:01:05 +0100 Subject: [PATCH] implement day 17 this is horrible, I'm not proud of it. There surely is some mathematical feature that allows a smarter solution, but after I've not been able to find that for a while, I've decided to just brute force it. Due to that, I've not had any motivation to finish this solution on time or to remove the loads of redundant code I've written here.. --- src/day_17/mod.rs | 53 ++++++++++++ src/day_17/model.rs | 190 ++++++++++++++++++++++++++++++++++++++++++ src/day_17/parsing.rs | 42 ++++++++++ src/lib.rs | 1 + 4 files changed, 286 insertions(+) create mode 100644 src/day_17/mod.rs create mode 100644 src/day_17/model.rs create mode 100644 src/day_17/parsing.rs diff --git a/src/day_17/mod.rs b/src/day_17/mod.rs new file mode 100644 index 0000000..0d196b8 --- /dev/null +++ b/src/day_17/mod.rs @@ -0,0 +1,53 @@ +mod model; +mod parsing; + +use aoc_runner_derive::{aoc, aoc_generator}; +pub use model::{Probe, TargetArea}; +pub use parsing::parse_target_area; +use yap::IntoTokens; + +#[aoc_generator(day17)] +pub fn parse_input(input: &str) -> TargetArea { + parse_target_area(&mut input.into_tokens()).unwrap() +} + +#[aoc(day17, part1)] +pub fn part1(input: &TargetArea) -> usize { + let y = input.get_highest_possible_y_velocity().unwrap(); + + let mut ret_val = 0; + for i in 1..=y { + ret_val += i + } + ret_val as usize +} + +#[aoc(day17, part2)] +pub fn part2(input: &TargetArea) -> usize { + input.get_all_possible_velocities().len() +} + +#[cfg(test)] +mod test { + + const EXAMPLE_INPUT: &str = "target area: x=20..30, y=-10..-5"; + const RESULT_PART_1: usize = 45; + const RESULT_PART_2: usize = 112; + + #[test] + fn part1_example() { + let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_1); + } + + #[test] + fn part2_example() { + let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_2); + } + + #[test] + fn parse_example() { + super::parse_input(EXAMPLE_INPUT); + } +} diff --git a/src/day_17/model.rs b/src/day_17/model.rs new file mode 100644 index 0000000..c7246a3 --- /dev/null +++ b/src/day_17/model.rs @@ -0,0 +1,190 @@ +use std::ops::RangeBounds; + +use itertools::Itertools; + +#[derive(PartialEq, Eq, Debug)] +pub struct TargetArea(pub(crate) (isize, isize), pub(crate) (isize, isize)); + +#[derive(PartialEq, Eq)] +pub enum State { + EnRoute, + InTargetArea, + Overshot, +} + +impl TargetArea { + pub fn get_position_state(&self, position: (isize, isize)) -> State { + let x_bounds_en_route = self.get_bounds(true, true); + let y_bounds_en_route = self.get_bounds(false, true); + let x_bounds_target = self.get_bounds(true, false); + let y_bounds_target = self.get_bounds(false, false); + match ( + x_bounds_en_route.contains(&position.0), + y_bounds_en_route.contains(&position.1), + x_bounds_target.contains(&position.0), + y_bounds_target.contains(&position.1), + ) { + (_, _, true, true) => State::InTargetArea, + (true, true, _, _) => State::EnRoute, + _ => State::Overshot, + } + } + + pub fn get_bounds(&self, x: bool, en_route: bool) -> impl RangeBounds { + let mut values = if x { + vec![self.0 .0, self.0 .1] + } else { + vec![self.1 .0, self.1 .1] + }; + if en_route && x { + values.push(0); + } else if en_route && !x { + values.push(isize::MAX); + } + *values.iter().min().unwrap()..=*values.iter().max().unwrap() + } + + pub fn get_lowest_possible_x_velocity(&self) -> Option { + let bounds = self.get_bounds(true, false); + for i in 1..=Ord::max(self.0 .0, self.0 .1) { + if XLoc(0, i).any(|loc| bounds.contains(&loc)) { + return Some(i); + } + } + None + } + + pub fn get_highest_possible_y_velocity(&self) -> Option { + let bounds = self.get_bounds(false, false); + let y_min = Ord::min(self.1 .0, self.1 .1); + (y_min..500) + .rev() + .find(|i| YLoc(0, *i, y_min).any(|loc| bounds.contains(&loc))) + } + + pub fn get_all_possible_velocities(&self) -> Vec<(isize, isize)> { + let x_bounds = self.get_bounds(true, false); + let x_velocities = (1..=Ord::max(self.0 .0, self.0 .1)) + .filter(|i| XLoc(0, *i).any(|loc| x_bounds.contains(&loc))); + let y_bounds = self.get_bounds(false, false); + let y_min = Ord::min(self.1 .0, self.1 .1); + let y_velocities = (y_min..500) + .rev() + .filter(|i| YLoc(0, *i, y_min).any(|loc| y_bounds.contains(&loc))); + + let mut ret_val = Vec::new(); + + x_velocities + .cartesian_product(y_velocities) + .filter(|velocity| Probe::new(*velocity).will_reach_target_area(self)) + .for_each(|velocity| ret_val.push(velocity)); + + ret_val + } +} + +pub struct XLoc(isize, isize); + +impl Iterator for XLoc { + type Item = isize; + + fn next(&mut self) -> Option { + let velocity = self.1; + self.0 += velocity; + let item = self.0; + match velocity { + n if n > 0 => self.1 -= 1, + n if n < 0 => self.1 += 1, + _ => return None, + }; + Some(item) + } +} + +pub struct YLoc(isize, isize, isize); + +impl Iterator for YLoc { + type Item = isize; + + fn next(&mut self) -> Option { + let velocity = self.1; + self.0 += velocity; + self.1 -= 1; + let item = self.0; + if item < self.2 { + None + } else { + Some(item) + } + } +} + +#[derive(Clone, Debug)] +pub struct Probe { + pub position: (isize, isize), + pub velocity: (isize, isize), +} + +impl Probe { + pub fn new(velocity: (isize, isize)) -> Self { + Self { + position: (0, 0), + velocity, + } + } + pub fn step(&mut self) { + self.position.0 += self.velocity.0; + self.position.1 += self.velocity.1; + self.velocity.0 = match self.velocity.0 { + n if n > 0 => self.velocity.0 - 1, + n if n < 0 => self.velocity.0 + 1, + _ => 0, + }; + self.velocity.1 -= 1; + } + pub fn will_reach_target_area(&self, target_area: &TargetArea) -> bool { + let mut probe = self.clone(); + while target_area.get_position_state(probe.position) == State::EnRoute { + probe.step(); + } + matches!( + target_area.get_position_state(probe.position), + State::InTargetArea + ) + } +} + +#[cfg(test)] +mod test { + use crate::day_17::model::Probe; + + use super::TargetArea; + + fn example() -> TargetArea { + TargetArea((20, 30), (-10, -5)) + } + + #[test] + fn examples() { + for velocity in [(7, 2), (6, 3), (9, 0), (6, 9)] { + assert!(Probe::new(velocity).will_reach_target_area(&example())); + } + for velocity in [(17, -4)] { + assert!(!Probe::new(velocity).will_reach_target_area(&example())); + } + + for y_vel in 10..1000 { + assert!(!Probe::new((6, y_vel)).will_reach_target_area(&example())); + } + } + + #[test] + fn lowest_possible_x_velocity() { + assert_eq!(example().get_lowest_possible_x_velocity(), Some(6)); + } + + #[test] + fn highest_possible_y_velocity() { + assert_eq!(example().get_highest_possible_y_velocity(), Some(9)); + } +} diff --git a/src/day_17/parsing.rs b/src/day_17/parsing.rs new file mode 100644 index 0000000..d3b94f1 --- /dev/null +++ b/src/day_17/parsing.rs @@ -0,0 +1,42 @@ +use yap::Tokens; + +use crate::parsing::{parse_n, parse_number}; + +use super::model::TargetArea; + +pub fn parse_target_area(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + if !t.tokens("target area: x=".chars()) { + return None; + } + let x = parse_bounds(t)?; + if !t.tokens(", y=".chars()) { + return None; + } + let y = parse_bounds(t)?; + Some(TargetArea(x, y)) + }) +} + +pub fn parse_bounds(tokens: &mut impl Tokens) -> Option<(isize, isize)> { + tokens.optional(|t| { + let numbers: Option<[isize; 2]> = + parse_n(t, |t| parse_number(t), |t| t.tokens("..".chars())); + numbers.map(|numbers| (numbers[0], numbers[1])) + }) +} + +#[cfg(test)] +mod test { + use yap::IntoTokens; + + use crate::day_17::model::TargetArea; + + const EXAMPLE: &str = "target area: x=20..30, y=-10..-5"; + + #[test] + fn parse_example() { + let example = super::parse_target_area(&mut EXAMPLE.into_tokens()); + assert_eq!(example, Some(TargetArea((20, 30), (-10, -5)))); + } +} diff --git a/src/lib.rs b/src/lib.rs index 79f09a2..c1ccb17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod day_13; pub mod day_14; pub mod day_15; pub mod day_16; +pub mod day_17; pub mod day_18; aoc_lib! { year = 2021 }