Compare commits
4 commits
7c8b242698
...
580f6259bc
Author | SHA1 | Date | |
---|---|---|---|
580f6259bc | |||
c49814908e | |||
71be88574c | |||
81b7e84097 |
53
src/day_17/mod.rs
Normal file
53
src/day_17/mod.rs
Normal 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
190
src/day_17/model.rs
Normal 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
42
src/day_17/parsing.rs
Normal 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
68
src/day_18/mod.rs
Normal 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
258
src/day_18/model.rs
Normal 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
68
src/day_18/parsing.rs
Normal 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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>(
|
||||
|
|
Loading…
Reference in a new issue