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..
This commit is contained in:
Jan Christian Grünhage 2021-12-18 18:01:05 +01:00
parent c49814908e
commit 580f6259bc
4 changed files with 286 additions and 0 deletions

53
src/day_17/mod.rs Normal file
View file

@ -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);
}
}

190
src/day_17/model.rs Normal file
View file

@ -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<isize> {
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<isize> {
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<isize> {
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<Self::Item> {
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<Self::Item> {
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));
}
}

42
src/day_17/parsing.rs Normal file
View file

@ -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<Item = char>) -> Option<TargetArea> {
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<Item = char>) -> 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))));
}
}

View file

@ -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 }