Compare commits

...

4 commits

Author SHA1 Message Date
Jan Christian Grünhage 580f6259bc 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..
2021-12-18 18:10:16 +01:00
Jan Christian Grünhage c49814908e implement day 18 2021-12-18 18:08:53 +01:00
Jan Christian Grünhage 71be88574c make parse_number fallible 2021-12-18 17:28:06 +01:00
Jan Christian Grünhage 81b7e84097 support parsing negative numbers 2021-12-18 12:54:51 +01:00
8 changed files with 698 additions and 11 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))));
}
}

68
src/day_18/mod.rs Normal file
View file

@ -0,0 +1,68 @@
mod model;
mod parsing;
use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools;
pub use model::SnailfishNumber;
pub use parsing::parse_number_list;
use yap::IntoTokens;
#[aoc_generator(day18)]
pub fn parse_input(input: &str) -> Vec<SnailfishNumber> {
parse_number_list(&mut input.into_tokens()).unwrap()
}
#[aoc(day18, part1)]
pub fn part1(input: &[SnailfishNumber]) -> isize {
let mut iter = input.iter();
let mut accu = iter.next().unwrap().clone();
for number in iter {
accu = accu + number.clone();
}
accu.magnitude()
}
#[aoc(day18, part2)]
pub fn part2(input: &[SnailfishNumber]) -> isize {
input
.iter()
.cloned()
.cartesian_product(input.iter().cloned())
.filter(|(a, b)| a != b)
.map(|(a, b)| (a + b).magnitude())
.max()
.unwrap()
}
#[cfg(test)]
mod test {
const EXAMPLE_INPUT: &str = "[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
[[[5,[2,8]],4],[5,[[9,9],0]]]
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
[[[[5,4],[7,7]],8],[[8,3],8]]
[[9,3],[[9,9],[6,[4,9]]]]
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]";
const RESULT_PART_1: isize = 4140;
const RESULT_PART_2: isize = 3993;
#[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);
}
}

258
src/day_18/model.rs Normal file
View file

@ -0,0 +1,258 @@
use std::fmt::Debug;
use std::ops::Deref;
use std::{borrow::Borrow, ops::Add};
#[derive(Clone, PartialEq)]
pub enum SnailfishNumber {
Pair(Box<SnailfishNumber>, Box<SnailfishNumber>),
Regular(isize),
}
impl Debug for SnailfishNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SnailfishNumber::Pair(lhs, rhs) => {
f.write_str("[")?;
Debug::fmt(&lhs, f)?;
f.write_str(",")?;
Debug::fmt(&rhs, f)?;
f.write_str("]")?;
Ok(())
}
SnailfishNumber::Regular(n) => Debug::fmt(n, f),
}
}
}
impl<T> From<T> for SnailfishNumber
where
T: Borrow<Box<SnailfishNumber>>,
{
fn from(s: T) -> Self {
s.borrow().deref().clone()
}
}
impl From<isize> for SnailfishNumber {
fn from(s: isize) -> Self {
Self::Regular(s)
}
}
impl From<&isize> for SnailfishNumber {
fn from(s: &isize) -> Self {
Self::Regular(*s)
}
}
impl<LHS, RHS> From<(LHS, RHS)> for SnailfishNumber
where
LHS: Into<SnailfishNumber>,
RHS: Into<SnailfishNumber>,
{
fn from(s: (LHS, RHS)) -> Self {
Self::Pair(Box::new(s.0.into()), Box::new(s.1.into()))
}
}
//impl From<&SnailfishNumber> for SnailfishNumberIterator {
// fn from(inner: &SnailfishNumber) -> Self {
// SnailfishNumberIterator { inner: inner.clone(), index: 0 }
// }
//}
//
//pub struct SnailfishNumberIterator {
// inner: SnailfishNumber,
// index: usize,
//}
//
//impl Iterator for SnailfishNumberIterator {
// type Item = isize;
//
// fn next(&mut self) -> Option<Self::Item> {
// let next = match self.inner {
// SnailfishNumber::Pair(rhs, lhs) => {
// let rhs = SnailfishNumberIterator::from(rhs.deref());
// let lhs = SnailfishNumberIterator::from(lhs.deref());
// let chain = rhs.chain(lhs);
// chain.nth(self.index)
// },
// SnailfishNumber::Regular(n) => {
// if self.index == 0 {
// Some(n)
// } else {
// None
// }
// }
// };
// if let Some(next) = next {
// self.index += 1;
// Some(next)
// } else {
// None
// }
// }
//}
impl SnailfishNumber {
pub fn regular(&self) -> Option<isize> {
match self {
SnailfishNumber::Regular(n) => Some(*n),
_ => None,
}
}
pub fn magnitude(&self) -> isize {
match self {
SnailfishNumber::Pair(lhs, rhs) => 3 * lhs.magnitude() + 2 * rhs.magnitude(),
SnailfishNumber::Regular(reg) => *reg,
}
}
pub fn reduce(self) -> Self {
let mut new = self;
while let (changed, true) = new.reduce_once() {
new = changed;
}
new
}
pub fn reduce_once(&self) -> (Self, bool) {
if let (_, changed, _, true) = self.explode(0) {
println!("after explode: {:?}", &changed);
(changed, true)
} else if let (changed, true) = self.split() {
println!("after split: {:?}", &changed);
(changed, true)
} else {
(self.clone(), false)
}
}
pub fn split(&self) -> (Self, bool) {
match self {
Self::Regular(n) if n >= &10 => {
let lhs = ((*n as f64) / 2.0).floor() as isize;
let rhs = ((*n as f64) / 2.0).ceil() as isize;
((lhs, rhs).into(), true)
}
Self::Regular(n) => (n.into(), false),
Self::Pair(lhs, rhs) => match lhs.split() {
(lhs, true) => ((lhs, rhs).into(), true),
(_, false) => match rhs.split() {
(rhs, true) => (Self::Pair(lhs.clone(), Box::new(rhs)), true),
(_, false) => (self.clone(), false),
},
},
}
}
pub fn explode(&self, depth: usize) -> (Option<isize>, Self, Option<isize>, bool) {
let new = self.clone();
match new {
SnailfishNumber::Pair(mut lhs, mut rhs) => {
if depth == 4 {
let (lhs, rhs) = (lhs.regular().unwrap(), rhs.regular().unwrap());
(Some(lhs), 0.into(), Some(rhs), true)
} else {
let (left_addition, inner, right_addition, exploded) = lhs.explode(depth + 1);
if exploded {
if let Some(right_addition) = right_addition {
rhs.add_right(right_addition);
}
return (left_addition, (inner, rhs).into(), None, true);
}
let (left_addition, inner, right_addition, exploded) = rhs.explode(depth + 1);
if exploded {
if let Some(left_addition) = left_addition {
lhs.add_left(left_addition);
}
return (None, (lhs, inner).into(), right_addition, true);
}
(None, self.clone(), None, false)
}
}
regular => (None, regular, None, false),
}
}
fn add_left(&mut self, other: isize) {
match self {
Self::Pair(_, rhs) => rhs.add_left(other),
Self::Regular(n) => *n += other,
}
}
fn add_right(&mut self, other: isize) {
match self {
Self::Pair(lhs, _) => lhs.add_right(other),
Self::Regular(n) => *n += other,
}
}
}
impl Add for SnailfishNumber {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::from((self, rhs)).reduce()
}
}
#[cfg(test)]
mod test {
use super::SnailfishNumber;
#[test]
fn multiple_reductions_example() {
let lhs: SnailfishNumber = ((((4, 3), 4), 4), (7, ((8, 4), 9))).into();
let rhs: SnailfishNumber = (1, 1).into();
let result: SnailfishNumber = ((((0, 7), 4), ((7, 8), (6, 0))), (8, 1)).into();
assert_eq!(lhs + rhs, result);
}
#[test]
fn explosion_1() {
let number: SnailfishNumber = (((((9, 8), 1), 2), 3), 4).into();
let result: SnailfishNumber = ((((0, 9), 2), 3), 4).into();
assert_eq!(number.reduce(), result);
}
#[test]
fn explosion_2() {
let number: SnailfishNumber = (7, (6, (5, (4, (3, 2))))).into();
let result: SnailfishNumber = (7, (6, (5, (7, 0)))).into();
assert_eq!(number.reduce(), result);
}
#[test]
fn explosion_3() {
let number: SnailfishNumber = ((6, (5, (4, (3, 2)))), 1).into();
let result: SnailfishNumber = ((6, (5, (7, 0))), 3).into();
assert_eq!(number.reduce(), result);
}
#[test]
fn explosion_4() {
let number: SnailfishNumber = ((3, (2, (1, (7, 3)))), (6, (5, (4, (3, 2))))).into();
let result: SnailfishNumber = ((3, (2, (8, 0))), (9, (5, (4, (3, 2))))).into();
assert_eq!(number.reduce_once(), (result, true));
}
#[test]
fn explosion_5() {
let number: SnailfishNumber = ((3, (2, (8, 0))), (9, (5, (4, (3, 2))))).into();
let result: SnailfishNumber = ((3, (2, (8, 0))), (9, (5, (7, 0)))).into();
assert_eq!(number.reduce(), result);
}
#[test]
fn magnitude() {
let number: SnailfishNumber = (
(((8, 7), (7, 7)), ((8, 6), (7, 7))),
(((0, 7), (6, 6)), (8, 7)),
)
.into();
assert_eq!(number.magnitude(), 3488);
}
}

68
src/day_18/parsing.rs Normal file
View file

@ -0,0 +1,68 @@
use yap::Tokens;
use crate::parsing::{newline, parse_digit};
use super::model::SnailfishNumber;
pub fn parse_regular(tokens: &mut impl Tokens<Item = char>) -> Option<SnailfishNumber> {
parse_digit(tokens).map(|digit: isize| digit.into())
}
pub fn parse_pair(tokens: &mut impl Tokens<Item = char>) -> Option<SnailfishNumber> {
tokens.optional(|t| {
if !t.token('[') {
return None;
}
let lhs = parse_snailfish_number(t)?;
if !t.token(',') {
return None;
}
let rhs = parse_snailfish_number(t)?;
if !t.token(']') {
return None;
}
Some((lhs, rhs).into())
})
}
pub fn parse_snailfish_number(tokens: &mut impl Tokens<Item = char>) -> Option<SnailfishNumber> {
yap::one_of!(t from tokens;
parse_regular(t),
parse_pair(t),
)
}
pub fn parse_number_list(tokens: &mut impl Tokens<Item = char>) -> Option<Vec<SnailfishNumber>> {
tokens.optional(|t| {
let vec: Vec<SnailfishNumber> = t
.sep_by(|t| parse_snailfish_number(t), |t| newline(t))
.collect();
if !vec.is_empty() {
Some(vec)
} else {
None
}
})
}
#[cfg(test)]
mod test {
use yap::IntoTokens;
#[test]
fn test_parsing() {
let number = super::parse_snailfish_number(
&mut "[[[[1,3],[5,3]],[[1,3],[8,7]]],[[[4,9],[6,9]],[[8,2],[7,3]]]]".into_tokens(),
);
assert_eq!(
number,
Some(
(
(((1, 3), (5, 3)), ((1, 3), (8, 7))),
(((4, 9), (6, 9)), ((8, 2), (7, 3)))
)
.into()
)
);
}
}

View file

@ -18,5 +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 }

View file

@ -22,17 +22,23 @@ pub fn parse_number_with_radix<T: Num>(
base: u32,
limit: Option<usize>,
) -> Option<T> {
tokens.skip_tokens_while(|t| *t == ' ');
let mut remaining = limit.map(|x| x as isize).unwrap_or(isize::MAX);
let digits: String = tokens
.tokens_while(|c| {
remaining -= 1;
c.is_digit(base) && remaining >= 0
})
.collect();
(!digits.is_empty())
.then(|| T::from_str_radix(&digits, base).ok())
.flatten()
tokens.optional(|t| {
t.skip_tokens_while(|t| *t == ' ');
let mut remaining = limit.map(|x| x as isize).unwrap_or(isize::MAX);
let mut prefix = String::new();
if t.token('-') {
prefix.push('-');
}
let digits: String = t
.tokens_while(|c| {
remaining -= 1;
c.is_digit(base) && remaining >= 0
})
.collect();
(!digits.is_empty())
.then(|| T::from_str_radix(&format!("{}{}", prefix, digits), base).ok())
.flatten()
})
}
pub fn parse_n<R, T, I, P, S, const N: usize>(