From 74542d76b86f99fb328570e8297412b05cac3f2d Mon Sep 17 00:00:00 2001 From: Lars Martens Date: Sat, 18 Dec 2021 23:06:34 +0100 Subject: [PATCH] Day 18 --- input/18-test.txt | 10 ++ input/18.txt | 100 ++++++++++++++++++ src/bin/18.rs | 258 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 input/18-test.txt create mode 100644 input/18.txt create mode 100644 src/bin/18.rs diff --git a/input/18-test.txt b/input/18-test.txt new file mode 100644 index 0000000..1368dc4 --- /dev/null +++ b/input/18-test.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]]] diff --git a/input/18.txt b/input/18.txt new file mode 100644 index 0000000..de44c92 --- /dev/null +++ b/input/18.txt @@ -0,0 +1,100 @@ +[[[[2,8],[4,6]],[[2,4],[9,4]]],[[[0,6],[4,6]],[1,6]]] +[7,[[5,7],1]] +[[[[8,8],7],5],[[[5,6],1],6]] +[[[8,5],[[0,0],[4,9]]],[2,8]] +[7,[[5,2],[[3,0],[7,7]]]] +[[6,[6,8]],[3,[5,2]]] +[6,[[[8,9],[9,9]],[3,8]]] +[[[1,[0,2]],[7,[3,0]]],8] +[[9,6],6] +[[[2,3],1],[9,[3,7]]] +[5,[[[5,8],3],9]] +[[[[8,8],3],[2,2]],[2,3]] +[[[4,9],3],[[[7,3],8],5]] +[[[3,5],[3,7]],[[[9,7],9],[9,[7,8]]]] +[[7,1],8] +[0,[[[6,8],[1,1]],[1,[5,8]]]] +[[[[2,2],[9,5]],[0,[1,0]]],[4,[[2,4],4]]] +[[[[2,5],[7,3]],[7,6]],[[6,[4,4]],[3,8]]] +[[3,[[7,9],2]],[[0,[4,4]],[[6,9],9]]] +[[[7,7],[[1,4],[1,6]]],[7,[[6,3],6]]] +[[0,8],[[[1,6],2],4]] +[[0,[[2,7],[0,4]]],[[[3,8],[7,7]],5]] +[[[[9,9],[1,3]],[9,[4,3]]],[[[3,4],[6,4]],1]] +[[[9,[0,9]],[2,[7,6]]],[2,[[1,9],[3,3]]]] +[[4,[5,6]],[[[1,5],6],[[1,5],[5,2]]]] +[1,[[3,[2,1]],5]] +[[4,[3,8]],[3,[6,3]]] +[[7,1],[[3,[6,0]],[5,[1,1]]]] +[[8,7],[[[0,1],[2,6]],[5,[4,7]]]] +[9,[[[1,6],[8,9]],[6,6]]] +[4,9] +[[[[0,8],[8,5]],9],[7,[1,3]]] +[[[[8,5],0],[[4,6],4]],[8,4]] +[[[[8,9],8],[[3,1],[7,6]]],2] +[[[[6,3],0],[2,[4,8]]],[[[0,3],[3,5]],4]] +[0,[[9,[0,6]],5]] +[[[[1,9],[2,7]],[[4,0],[9,9]]],[[8,[3,6]],[3,4]]] +[[[[0,7],[8,4]],1],[[8,3],[[3,5],[8,0]]]] +[[[[3,5],4],[0,9]],[[[1,7],5],[9,[8,0]]]] +[[[8,[6,8]],[[3,7],[0,8]]],[[[5,2],[1,7]],[9,5]]] +[[[[5,1],[0,7]],4],[0,4]] +[[[[9,8],[3,9]],[[0,6],3]],[[[9,1],[8,7]],2]] +[[9,[[0,3],6]],[[3,4],[[8,9],5]]] +[[1,[1,8]],[[6,[4,2]],1]] +[7,[[1,[5,2]],[[9,7],0]]] +[0,[8,6]] +[1,4] +[[8,[4,1]],[[[4,0],[0,0]],[7,[3,4]]]] +[2,[[1,[1,8]],[[3,4],1]]] +[[8,[[1,2],[3,1]]],[[[4,4],[7,9]],1]] +[[4,[0,[6,4]]],[9,[0,[1,2]]]] +[[6,[3,1]],[[7,8],[8,[2,5]]]] +[[[2,[3,3]],[[6,4],[9,4]]],[[[1,5],[7,4]],[0,6]]] +[[[[8,0],3],[[4,0],3]],[[7,5],4]] +[[[2,[4,3]],[[2,1],5]],1] +[[[8,1],[0,4]],[9,[[1,4],[9,0]]]] +[[[5,0],[[7,7],9]],[[6,[6,2]],7]] +[[[[5,9],0],[[4,6],[3,8]]],[6,[6,5]]] +[[[6,[7,8]],[5,3]],[[3,[6,5]],[[8,7],[4,7]]]] +[[9,[[8,7],4]],[[[6,3],0],[[2,3],[5,9]]]] +[[[[1,8],6],1],[[[7,8],4],[7,2]]] +[[[[7,1],[6,2]],[[7,8],2]],0] +[[[4,5],[0,3]],[[2,4],1]] +[[[9,1],7],[[[8,8],[0,7]],[8,0]]] +[[5,[[7,5],[7,5]]],[3,[4,8]]] +[[7,[1,0]],[[3,[1,5]],0]] +[[[5,1],[[5,2],[7,3]]],[[7,[3,9]],9]] +[5,[1,[[9,9],[3,0]]]] +[[2,0],[9,[6,[3,3]]]] +[[[[0,4],[4,8]],[[1,9],[5,8]]],[[[7,0],5],[5,1]]] +[[[[1,5],[9,2]],[6,[3,6]]],[4,[1,[1,5]]]] +[[[[1,4],[4,6]],[[5,5],[3,5]]],[[[7,1],4],[[0,7],4]]] +[[6,[3,5]],1] +[8,[[1,[0,7]],[[2,5],6]]] +[[[[1,6],3],[[9,7],9]],[[7,8],3]] +[[[[9,9],[2,0]],0],[1,4]] +[[[[1,3],[5,1]],[[0,4],2]],0] +[[3,2],[7,[[9,3],8]]] +[[9,0],[4,[[8,7],[5,5]]]] +[[[[7,4],8],[[4,4],1]],9] +[[9,[[7,9],1]],[[[6,5],7],[[2,5],2]]] +[7,2] +[[[6,6],[[9,4],4]],6] +[[1,[[5,0],3]],[5,[4,4]]] +[[[3,2],[[4,6],6]],[[3,[9,5]],[[0,2],[4,6]]]] +[5,[[0,[3,0]],[7,[7,9]]]] +[[[[0,4],[1,5]],4],[8,[[4,7],8]]] +[[[[9,1],0],0],4] +[[[[8,4],[4,2]],[9,[1,7]]],[6,3]] +[2,[[[8,3],2],[[3,1],8]]] +[[[[9,0],[7,8]],[[2,7],[0,3]]],[[[8,5],3],[9,[6,8]]]] +[[[[8,9],[9,1]],[4,[0,1]]],[[[7,8],2],2]] +[[[[2,2],[4,1]],[2,[2,8]]],[[[6,5],1],9]] +[[[[3,0],7],7],[[[9,3],7],4]] +[[[[7,5],1],3],[[[0,7],7],[[2,6],[9,9]]]] +[[[[5,2],8],[9,[8,8]]],[2,[[0,8],[5,6]]]] +[[[[7,7],[1,2]],[6,6]],[8,[5,8]]] +[[7,[4,[8,9]]],[[4,[7,2]],8]] +[[[6,4],[7,7]],[[[3,7],0],[0,1]]] +[[1,[5,9]],[8,[4,6]]] diff --git a/src/bin/18.rs b/src/bin/18.rs new file mode 100644 index 0000000..ec0b9a9 --- /dev/null +++ b/src/bin/18.rs @@ -0,0 +1,258 @@ +use std::ops::Add; + +fn main() { + println!( + "First solution: {}", + part1(include_str!("../../input/18.txt")) + ); + println!( + "Second solution: {}", + part2(include_str!("../../input/18.txt")) + ); +} + +fn part1(s: &str) -> u32 { + s.lines() + .map(Number::from) + .reduce(|a, b| a + b) + .unwrap() + .magnitude() +} + +fn part2(s: &str) -> u32 { + let numbers: Vec = s.lines().map(Number::from).collect(); + let mut max = 0; + + for a in &numbers { + for b in &numbers { + let sum = (a.clone() + b.clone()).magnitude(); + max = max.max(sum); + } + } + + max +} + +#[derive(Debug, Clone, Eq)] +struct Number(Vec); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Token { + Open, + Close, + Num(u32), +} + +impl Token { + /// assume that this token is a number and return the value or panic. + fn num(&self) -> u32 { + match self { + Token::Num(n) => *n, + t => panic!("expected Token::Num, but got {:?}", t), + } + } + + fn num_mut(&mut self) -> &mut u32 { + match self { + Token::Num(n) => n, + t => panic!("expected Token::Num, but got {:?}", t), + } + } +} + +impl Add for Number { + type Output = Number; + + fn add(self, rhs: Self) -> Self::Output { + let mut v = vec![Token::Open]; + v.extend(self.0); + v.extend(rhs.0); + v.push(Token::Close); + Self(v).reduce() + } +} + +impl From<&str> for Number { + fn from(s: &str) -> Self { + Self( + s.chars() + .filter_map(|c| match c { + '[' => Some(Token::Open), + ']' => Some(Token::Close), + ',' => None, // commas do not matter after parsing + d => match d.to_digit(10) { + Some(d) => Some(Token::Num(d)), + None => panic!("unknown char '{}'", d), + }, + }) + .collect(), + ) + } +} + +impl PartialEq for Number { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Number { + fn magnitude(&self) -> u32 { + let mut stack: Vec = Vec::new(); + + for tok in &self.0 { + match tok { + Token::Open => continue, + Token::Close => { + let b = stack.pop().unwrap(); + let a = stack.pop().unwrap(); + stack.push(3 * a + 2 * b); + } + Token::Num(n) => stack.push(*n), + } + } + + stack.pop().unwrap() + } + + fn reduce(&self) -> Self { + let mut result = self.clone(); + + loop { + let (num, exploded) = result.explode(); + if exploded { + result = num; + } else { + let (num, split) = num.split(); + if split { + result = num; + } else { + return result; + } + } + } + } + + /// Perform the leftmost explosion, if any. Returns true if any explosions happened. + fn explode(&self) -> (Self, bool) { + let mut explode_happened = false; + let mut depth = 0; + + // Keep track of the last num to move a value backwards. + let mut last_num_idx: Option = None; + // Carry a value to the next num + let mut carrying: Option = None; + + let mut result: Vec = Vec::new(); + let mut tokens = self.0.iter().copied(); + + while let Some(tok) = tokens.next() { + match tok { + Token::Open if depth >= 4 && !explode_happened => { + // Explode! + explode_happened = true; + let left = tokens.next().unwrap().num(); + let right = tokens.next().unwrap().num(); + let _close = tokens.next(); + + if let Some(i) = last_num_idx { + *result[i].num_mut() += left; + } + carrying = Some(right); + + result.push(Token::Num(0)); + } + Token::Open => { + depth += 1; + result.push(Token::Open); + } + Token::Close => { + depth -= 1; + result.push(Token::Close); + } + Token::Num(n) => { + last_num_idx = Some(result.len()); + let n = n + carrying.take().unwrap_or(0); + result.push(Token::Num(n)); + } + } + } + + (Self(result), explode_happened) + } + + /// Perform the leftmost split, if any. Returns true if any splits happened. + fn split(&self) -> (Self, bool) { + let mut split_happened = false; + let mut result = Vec::new(); + + for tok in self.0.iter().copied() { + match tok { + Token::Num(n) if n > 9 && !split_happened => { + // Split! + split_happened = true; + let (a, b) = split(n); + result.extend([Token::Open, Token::Num(a), Token::Num(b), Token::Close]); + } + tok => result.push(tok), + } + } + + (Self(result), split_happened) + } +} + +fn split(n: u32) -> (u32, u32) { + if n % 2 == 0 { + (n / 2, n / 2) + } else { + (n / 2, n / 2 + 1) + } +} + +#[cfg(test)] +mod test { + use crate::{part1, part2, Number}; + + #[test] + fn magnitude() { + assert_eq!(Number::from("[[1,2],[[3,4],5]]").magnitude(), 143); + assert_eq!( + Number::from("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]").magnitude(), + 1384 + ); + assert_eq!( + Number::from("[[[[1,1],[2,2]],[3,3]],[4,4]]").magnitude(), + 445 + ); + assert_eq!( + Number::from("[[[[3,0],[5,3]],[4,4]],[5,5]]").magnitude(), + 791 + ); + assert_eq!( + Number::from("[[[[5,0],[7,4]],[5,5]],[6,6]]").magnitude(), + 1137 + ); + assert_eq!( + Number::from("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]").magnitude(), + 3488 + ); + } + + #[test] + fn explode() { + let expected = Number::from("[[[[0,9],2],3],4]"); + let actual = Number::from("[[[[[9,8],1],2],3],4]").reduce(); + assert_eq!(expected, actual) + } + + #[test] + fn example_part1() { + assert_eq!(part1(include_str!("../../input/18-test.txt")), 4140); + } + + #[test] + fn example_part2() { + assert_eq!(part2(include_str!("../../input/18-test.txt")), 3993); + } +}