From 7d331f91312e6aeac6b95e75739bd13a60674593 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sat, 18 Dec 2021 14:46:27 +0100 Subject: [PATCH] Implement day 18 2021 --- 2021/benches/days.rs | 2 +- 2021/inputs/18.txt | 100 ++++++++++++++++ 2021/src/common.rs | 15 +++ 2021/src/day18.rs | 244 +++++++++++++++++++++++++++++++++++++++- 2021/src/samples/18.txt | 10 ++ 5 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 2021/inputs/18.txt create mode 100644 2021/src/samples/18.txt diff --git a/2021/benches/days.rs b/2021/benches/days.rs index bdb978a..cfd9414 100644 --- a/2021/benches/days.rs +++ b/2021/benches/days.rs @@ -7,7 +7,7 @@ use criterion::criterion_main; use criterion::BenchmarkId; use criterion::Criterion; -const DAYS_IMPLEMENTED: usize = 16; +const DAYS_IMPLEMENTED: usize = 18; fn read_input(day: usize) -> Vec { let input_path = format!("inputs/{:02}.txt", day); diff --git a/2021/inputs/18.txt b/2021/inputs/18.txt new file mode 100644 index 0000000..1aa1391 --- /dev/null +++ b/2021/inputs/18.txt @@ -0,0 +1,100 @@ +[[3,[[6,3],[9,6]]],[6,[[0,9],[9,7]]]] +[[[3,9],[[0,8],[7,6]]],[[[7,9],1],[1,3]]] +[8,[[[9,6],[8,4]],4]] +[5,[[1,2],[3,7]]] +[[[[7,7],5],[[3,5],8]],4] +[[[5,[0,7]],3],[[5,[5,3]],[1,[9,4]]]] +[[[[3,5],[7,1]],6],[[[3,6],[5,6]],[[3,2],5]]] +[[[[2,0],[3,0]],[5,7]],[[4,4],[[9,9],[9,3]]]] +[[[[8,0],7],[[7,1],9]],[[3,[8,6]],8]] +[[6,[7,5]],[[6,8],9]] +[[[9,[1,8]],2],[[[4,0],[9,3]],1]] +[[7,[1,[3,8]]],[[4,7],[8,1]]] +[[[5,5],[[4,5],[2,9]]],[[[7,7],0],8]] +[[[[4,7],3],5],[[[4,3],[3,8]],[[6,5],5]]] +[[[[3,8],2],[1,7]],[[[3,1],4],9]] +[[[[2,1],4],[[9,5],[1,4]]],[[3,5],[[9,1],9]]] +[[[6,[1,8]],[0,0]],[9,[0,3]]] +[[[[2,2],[3,3]],[[4,8],4]],[[[6,8],4],5]] +[4,[[[7,8],[3,4]],[[3,2],9]]] +[[[9,0],3],[[[7,1],4],7]] +[[[1,4],8],[[7,5],[[8,0],[0,7]]]] +[9,[[4,6],[[2,9],1]]] +[[[[1,8],8],6],[[[2,0],6],[0,5]]] +[[[5,5],[6,4]],[[3,8],[9,[7,6]]]] +[[0,[8,[1,4]]],2] +[[[[9,5],0],5],[9,[7,5]]] +[[9,[4,8]],[[8,1],[[8,6],[7,1]]]] +[4,[[[9,6],5],9]] +[[[[3,7],6],0],[[7,7],[[2,7],[9,3]]]] +[[[6,[3,7]],[[8,3],2]],[8,[6,[8,5]]]] +[[[5,[2,7]],[[6,7],3]],[5,[[4,4],1]]] +[[1,0],[[2,8],[[0,4],9]]] +[[[1,4],6],[[[9,8],[1,0]],1]] +[[3,4],[[1,[8,4]],8]] +[[[[9,4],[0,7]],[[5,4],[8,2]]],2] +[5,[[[8,7],[3,4]],[2,4]]] +[[[[1,3],[8,6]],[[3,4],6]],[[8,5],[[9,3],[5,7]]]] +[[0,[[0,9],[7,8]]],[3,9]] +[0,[[8,[2,3]],[[3,5],[4,9]]]] +[[[4,3],[[1,9],[1,5]]],[4,[[9,1],1]]] +[[[[3,6],[2,5]],3],[[8,[8,0]],[[6,9],[5,8]]]] +[7,[[3,[3,6]],[[6,9],[2,7]]]] +[[[[8,3],[6,5]],[[3,9],2]],[6,1]] +[[[2,0],[2,3]],8] +[[1,[[8,7],2]],[[[9,4],8],[4,[9,0]]]] +[[[6,7],[[5,2],3]],[[0,5],[[9,4],[2,6]]]] +[[[9,[5,8]],[[9,3],[6,9]]],5] +[[[5,[4,6]],[5,[3,2]]],[2,[9,[5,4]]]] +[8,6] +[[[4,8],[3,1]],[1,[[7,8],[7,5]]]] +[[4,[[8,8],4]],[5,[8,[3,9]]]] +[[[4,[9,0]],[[0,3],5]],[[5,[3,0]],[6,[2,3]]]] +[[[4,0],8],[[[4,0],7],[[9,6],3]]] +[[8,[[7,8],5]],[[[6,2],8],[1,[0,4]]]] +[[1,[[3,4],[0,8]]],[[6,5],3]] +[[5,2],[[8,6],[1,[9,7]]]] +[5,[6,[[1,3],[1,0]]]] +[[0,[[1,9],[5,6]]],[[[6,2],[5,1]],[[1,2],[1,0]]]] +[[[7,1],4],[[[0,3],3],[[4,8],1]]] +[[3,[9,[3,4]]],[1,[[0,0],[1,4]]]] +[1,[7,[1,[3,7]]]] +[[[0,[5,6]],[[7,4],[5,7]]],[[[6,8],[4,6]],9]] +[[[9,8],[7,[1,3]]],3] +[[[4,[0,3]],[[3,0],6]],[[2,[9,2]],1]] +[[[[1,9],[3,3]],[8,1]],5] +[[7,[5,2]],[[4,[0,1]],[3,3]]] +[[[6,6],[0,6]],[[3,[5,9]],[[4,2],[4,3]]]] +[[[7,[5,4]],[7,1]],9] +[[6,[5,2]],[[7,[0,5]],4]] +[[[8,1],[[7,6],[4,1]]],2] +[[[[4,3],[1,4]],[9,6]],[3,[[2,5],3]]] +[[[[9,3],[5,0]],1],[1,[[9,7],9]]] +[[[8,5],[5,9]],[2,[4,[0,0]]]] +[[[[7,9],2],[[8,8],[6,3]]],[7,[0,9]]] +[[[[6,6],[0,2]],[2,[9,0]]],[[0,9],[9,9]]] +[[[9,[1,3]],[6,5]],[[[1,1],8],[9,[7,2]]]] +[[8,[[8,4],6]],[[4,[5,9]],0]] +[[8,[5,[6,7]]],[[[1,9],9],[0,[0,9]]]] +[[9,[9,[7,3]]],[4,[4,7]]] +[[[[9,3],7],5],[[5,[8,5]],[0,[8,0]]]] +[[[5,[9,0]],[[7,4],[5,3]]],[3,[[1,1],[1,8]]]] +[[1,[[1,4],[5,9]]],[[[9,1],[6,5]],[9,[0,7]]]] +[[[[9,4],9],[5,3]],[[[4,2],[2,2]],[[1,0],0]]] +[[[6,[8,6]],9],[8,[[0,1],[9,7]]]] +[[2,0],[5,[[8,3],4]]] +[[[[0,2],0],8],[8,[[2,5],[8,2]]]] +[[[[7,4],8],[9,[7,5]]],[8,[7,[5,3]]]] +[[2,4],[3,[3,8]]] +[[5,4],[[0,[5,8]],[4,3]]] +[6,[[5,[4,7]],9]] +[[[2,[6,8]],[5,5]],[[[3,0],4],[[6,6],[0,1]]]] +[[[1,[4,2]],[[8,0],8]],[8,[[6,1],[0,0]]]] +[[9,[2,[3,3]]],[[2,6],[[5,2],[5,8]]]] +[[9,[4,4]],[[[8,6],1],2]] +[2,[[[0,7],7],[[7,8],5]]] +[[[4,0],[[1,1],[7,6]]],[[6,7],[[7,2],1]]] +[[[[2,5],0],[[9,5],9]],[6,[7,[6,1]]]] +[[[7,8],1],[[[6,2],0],[[9,7],[3,5]]]] +[[[9,1],0],[3,[[6,1],[6,9]]]] +[[[[9,0],0],[4,[7,0]]],[[6,[4,0]],[8,[4,2]]]] diff --git a/2021/src/common.rs b/2021/src/common.rs index 07311a6..6751a8f 100644 --- a/2021/src/common.rs +++ b/2021/src/common.rs @@ -4,6 +4,9 @@ use std::io::Read; use std::marker::PhantomData; use std::str::FromStr; +use nom::Finish; +use nom::IResult; + pub struct LineIter<'a> { reader: BufReader<&'a mut dyn Read>, buffer: String, @@ -78,3 +81,15 @@ pub fn ordered(a: O, b: O) -> (O, O) { (b, a) } } + +pub fn read_input(input: &mut dyn Read, parser: P) -> O +where + P: for<'a> FnOnce(&'a [u8]) -> IResult<&'a [u8], O>, +{ + let mut buffer = Vec::new(); + input.read_to_end(&mut buffer).unwrap(); + + let (_, output) = parser(&buffer).finish().unwrap(); + + output +} diff --git a/2021/src/day18.rs b/2021/src/day18.rs index 113ba49..bae86c6 100644 --- a/2021/src/day18.rs +++ b/2021/src/day18.rs @@ -1,9 +1,245 @@ +use std::fmt::Debug; use std::io::Read; +use std::mem::replace; -pub fn part1(_input: &mut dyn Read) -> String { - todo!() +use nom::branch::alt; +use nom::character::complete::newline; +use nom::combinator::map; +use nom::multi::many0; +use nom::sequence::delimited; +use nom::sequence::separated_pair; +use nom::sequence::terminated; +use nom::IResult; + +use crate::common::read_input; + +#[derive(Clone, Eq, PartialEq)] +enum TurtleNumber { + Literal(u8), + Pair(Box<(TurtleNumber, TurtleNumber)>), } -pub fn part2(_input: &mut dyn Read) -> String { - todo!() +impl TurtleNumber { + pub fn add(self, other: Self) -> Self { + let mut new = TurtleNumber::Pair(Box::new((self, other))); + new.reduce(); + + new + } + + pub fn magnitude(&self) -> u32 { + match self { + TurtleNumber::Literal(num) => *num as u32, + TurtleNumber::Pair(pair) => 3 * pair.0.magnitude() + 2 * pair.1.magnitude(), + } + } + + fn reduce(&mut self) { + loop { + while self.try_explode(0).is_some() {} + + if self.split() { + continue; + } else { + break; + } + } + } + + fn leftmost_add(&mut self, value: u8) { + match self { + TurtleNumber::Literal(num) => *num += value, + TurtleNumber::Pair(pair) => pair.0.leftmost_add(value), + } + } + + fn rightmost_add(&mut self, value: u8) { + match self { + TurtleNumber::Literal(num) => *num += value, + TurtleNumber::Pair(pair) => pair.1.rightmost_add(value), + } + } + + fn try_explode(&mut self, depth: usize) -> Option<(Option, Option)> { + let pair = match self { + TurtleNumber::Literal(_) => return None, + TurtleNumber::Pair(pair) => pair, + }; + + if depth == 4 { + let original = replace(self, TurtleNumber::Literal(0)); + let pair = match original { + TurtleNumber::Pair(pair) => *pair, + _ => unreachable!("Already checked for pair above"), + }; + + if let (TurtleNumber::Literal(first), TurtleNumber::Literal(second)) = pair { + Some((Some(first), Some(second))) + } else { + panic!("Too deeply nested turtle number: {:?}", pair); + } + } else { + match pair.0.try_explode(depth + 1) { + Some((left, Some(right))) => { + pair.1.leftmost_add(right); + Some((left, None)) + } + Some((left, None)) => Some((left, None)), + None => match pair.1.try_explode(depth + 1) { + Some((Some(left), right)) => { + pair.0.rightmost_add(left); + Some((None, right)) + } + other => other, + }, + } + } + } + + pub fn split(&mut self) -> bool { + match self { + TurtleNumber::Literal(num) if *num >= 10 => { + let half = *num / 2; + let other = *num - half; + *self = TurtleNumber::from((half, other)); + true + } + TurtleNumber::Pair(pair) => pair.0.split() || pair.1.split(), + _ => false, + } + } +} + +impl Debug for TurtleNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Literal(num) => write!(f, "{}", num), + Self::Pair(pair) => write!(f, "[{:?},{:?}]", pair.0, pair.1), + } + } +} + +// Helper traits to easily convert tuples to turtle numbers +impl From for TurtleNumber { + fn from(num: u8) -> Self { + TurtleNumber::Literal(num) + } +} + +impl From<(T, U)> for TurtleNumber +where + T: Into, + U: Into, +{ + fn from((left, right): (T, U)) -> Self { + TurtleNumber::Pair(Box::new((left.into(), right.into()))) + } +} + +fn parse_turtle(input: &[u8]) -> IResult<&[u8], TurtleNumber> { + use nom::character::complete::char; + use nom::character::complete::u8; + + alt(( + map(u8, TurtleNumber::Literal), + map( + delimited( + char('['), + separated_pair(parse_turtle, char(','), parse_turtle), + char(']'), + ), + |pair| TurtleNumber::Pair(Box::new(pair)), + ), + ))(input) +} + +fn parse_input(input: &[u8]) -> IResult<&[u8], Vec> { + many0(terminated(parse_turtle, newline))(input) +} + +pub fn part1(input: &mut dyn Read) -> String { + let turtles = read_input(input, parse_input); + turtles + .into_iter() + .reduce(|a, b| a.add(b)) + .map(|num| num.magnitude()) + .unwrap() + .to_string() +} + +pub fn part2(input: &mut dyn Read) -> String { + let turtles = read_input(input, parse_input); + + turtles + .iter() + .flat_map(|a| turtles.iter().map(|b| a.clone().add(b.clone()).magnitude())) + .max() + .unwrap() + .to_string() +} + +#[cfg(test)] +mod tests { + use crate::test_implementation; + + use super::*; + + const SAMPLE: &[u8] = include_bytes!("samples/18.txt"); + + #[test] + fn test_magnitude() { + let num: TurtleNumber = ( + (((8, 7), (7, 7)), ((8, 6), (7, 7))), + (((0, 7), (6, 6)), (8, 7)), + ) + .into(); + + assert_eq!(num.magnitude(), 3488); + } + + #[test] + fn test_add() { + let input = TurtleNumber::from(((((4, 3), 4), 4), (7, ((8, 4), 9)))); + let result = input.add((1, 1).into()); + + let expected = TurtleNumber::from(((((0, 7), 4), ((7, 8), (6, 0))), (8, 1))); + assert_eq!(result, expected); + } + + #[test] + fn test_explode() { + let mut input1 = TurtleNumber::from((((((9, 8), 1), 2), 3), 4)); + let output1 = TurtleNumber::from(((((0, 9), 2), 3), 4)); + + input1.reduce(); + assert_eq!(input1, output1); + + let mut input2 = TurtleNumber::from((7, (6, (5, (4, (3, 2)))))); + let output2 = TurtleNumber::from((7, (6, (5, (7, 0))))); + + input2.reduce(); + assert_eq!(input2, output2); + + let mut input3: TurtleNumber = TurtleNumber::from(((6, (5, (4, (3, 2)))), 1)); + let output3 = TurtleNumber::from(((6, (5, (7, 0))), 3)); + + input3.reduce(); + assert_eq!(input3, output3); + + let mut input4 = TurtleNumber::from(((3, (2, (1, (7, 3)))), (6, (5, (4, (3, 2)))))); + let output4 = TurtleNumber::from(((3, (2, (8, 0))), (9, (5, (7, 0))))); + + input4.reduce(); + assert_eq!(input4, output4); + } + + #[test] + fn sample_part1() { + test_implementation(part1, SAMPLE, 4140); + } + + #[test] + fn sample_part2() { + test_implementation(part2, SAMPLE, 3993); + } } diff --git a/2021/src/samples/18.txt b/2021/src/samples/18.txt new file mode 100644 index 0000000..1368dc4 --- /dev/null +++ b/2021/src/samples/18.txt @@ -0,0 +1,10 @@ +[[[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]]]