Add (messy) day 18 part 1 solution

master
Nick Krichevsky 2021-12-27 15:29:17 -05:00
parent ef54d76d28
commit 361a0004b4
4 changed files with 676 additions and 0 deletions

47
day18/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,47 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'day18'",
"cargo": {
"args": [
"build",
"--bin=day18",
"--package=day18"
],
"filter": {
"name": "day18",
"kind": "bin"
}
},
"args": [
"sample9.txt"
],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'day18'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=day18",
"--package=day18"
],
"filter": {
"name": "day18",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

78
day18/Cargo.lock generated Normal file
View File

@ -0,0 +1,78 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "day18"
version = "0.1.0"
dependencies = [
"nom",
"petgraph",
]
[[package]]
name = "fixedbitset"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
name = "petgraph"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"

10
day18/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day18"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nom = "7.1"
petgraph = "0.6"

541
day18/src/main.rs Normal file
View File

@ -0,0 +1,541 @@
#![warn(clippy::all, clippy::pedantic)]
use std::collections::HashSet;
use std::env;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::mem;
use nom::{
branch::alt,
character::complete::char,
character::complete::digit1,
combinator::{eof, map_res},
sequence::{delimited, separated_pair, terminated},
IResult,
};
use petgraph::dot::{Config, Dot};
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::StableDiGraph;
#[derive(Clone, Debug)]
enum InputPair {
Pair(Box<InputPair>, Box<InputPair>),
Leaf(u32),
}
#[derive(Clone, Copy, Debug)]
enum PairNode {
PairRoot,
Leaf(u32),
}
#[derive(Clone, Copy, Debug)]
enum Direction {
Left,
Right,
}
#[derive(Clone, Copy, Debug)]
enum EdgeType {
Parent,
Child(Direction),
}
#[derive(Clone)]
struct ProblemTree {
graph: StableDiGraph<PairNode, EdgeType>,
root_idx: NodeIndex,
}
impl Direction {
fn get_other(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
}
impl ProblemTree {
fn build(left: &InputPair, right: &InputPair) -> Self {
let mut graph = StableDiGraph::new();
let root_idx = graph.add_node(PairNode::PairRoot);
let mut tree = ProblemTree { graph, root_idx };
tree.insert_input_pair(left, Direction::Left, root_idx);
tree.insert_input_pair(right, Direction::Right, root_idx);
tree
}
fn insert_input_pair(&mut self, pair: &InputPair, direction: Direction, parent_idx: NodeIndex) {
match pair {
&InputPair::Leaf(n) => {
self.insert_leaf(n, direction, parent_idx);
}
InputPair::Pair(left, right) => {
let root_idx = self.graph.add_node(PairNode::PairRoot);
// We're inserting twice into the directional graph so we can differentiate between parent/child relationships
self.graph
.add_edge(parent_idx, root_idx, EdgeType::Child(direction));
self.graph.add_edge(root_idx, parent_idx, EdgeType::Parent);
self.insert_input_pair(left, Direction::Left, root_idx);
self.insert_input_pair(right, Direction::Right, root_idx);
}
}
}
/// inserts a pair to the tree that will be a sibling to the root, with a new root being planted in this tree
fn insert_root_sibling_input_pair(&mut self, pair: &InputPair, direction: Direction) {
let new_root_idx = self.graph.add_node(PairNode::PairRoot);
let old_root_idx = self.root_idx;
self.root_idx = new_root_idx;
self.graph.add_edge(
new_root_idx,
old_root_idx,
EdgeType::Child(direction.get_other()),
);
self.graph
.add_edge(old_root_idx, new_root_idx, EdgeType::Parent);
self.insert_input_pair(pair, direction, new_root_idx);
}
fn insert_leaf(&mut self, value: u32, direction: Direction, parent_idx: NodeIndex) {
let leaf_idx = self.graph.add_node(PairNode::Leaf(value));
// We're inserting twice into the directional graph so we can differentiate between parent/child relationships
self.graph
.add_edge(parent_idx, leaf_idx, EdgeType::Child(direction));
self.graph.add_edge(leaf_idx, parent_idx, EdgeType::Parent);
}
fn magnitude(&mut self) -> u32 {
let mut to_visit = vec![(1, self.root_idx)];
let mut total = 0;
while let Some((n, visiting_idx)) = to_visit.pop() {
let visiting = self.graph.node_weight(visiting_idx).unwrap();
let neighbors = self.graph.neighbors(visiting_idx);
match visiting {
PairNode::PairRoot => {
for neighbor in neighbors {
let edge_idx = self.graph.find_edge(visiting_idx, neighbor).unwrap();
let edge_type = self.graph.edge_weight(edge_idx).unwrap();
match edge_type {
EdgeType::Child(Direction::Left) => to_visit.push((3 * n, neighbor)),
EdgeType::Child(Direction::Right) => to_visit.push((2 * n, neighbor)),
EdgeType::Parent => (),
}
}
}
PairNode::Leaf(visiting_value) => {
total += n * visiting_value;
// for neighbor in neighbors {
// let edge_idx = self.graph.find_edge(visiting_idx, neighbor).unwrap();
// let edge_type = self.graph.edge_weight(edge_idx).unwrap();
// }
}
};
}
total
}
fn reduce(&mut self) {
let mut performed_action: Option<bool> = None;
while performed_action.unwrap_or(true) {
performed_action = Some(false);
let explode_candidate = self.find_node_to_explode();
let split_candidate = self.find_node_to_split();
if let Some(to_explode) = explode_candidate {
performed_action = Some(true);
self.explode_in_relative_direction(to_explode, Direction::Left);
self.explode_in_relative_direction(to_explode, Direction::Right);
let neighbors = self.graph.neighbors(to_explode).collect::<Vec<_>>();
for neighbor in neighbors {
if let PairNode::Leaf(_) = self.graph.node_weight(neighbor).unwrap() {
self.graph.remove_node(dbg!(neighbor));
}
}
let to_explode_weight = self.graph.node_weight_mut(to_explode).unwrap();
*to_explode_weight = PairNode::Leaf(0);
// print_tree(self);
dbg!(&self);
// std::io::stdin().read_line(&mut String::new());
}
if performed_action.unwrap_or(false) {
continue;
}
if let Some(to_split) = split_candidate {
performed_action = Some(true);
self.split_node(to_split);
dbg!(&self);
// std::io::stdin().read_line(&mut String::new());
}
}
}
fn find_node_to_explode(&self) -> Option<NodeIndex> {
self.find_node_to_reduce_below_or_at(self.root_idx, 0, |node, depth| {
depth >= 4 && matches!(node, PairNode::PairRoot)
})
}
fn find_node_to_split(&self) -> Option<NodeIndex> {
self.find_node_to_reduce_below_or_at(self.root_idx, 0, |node, _| {
if let PairNode::Leaf(n) = node {
n >= 10
} else {
false
}
})
}
fn find_node_to_reduce_below_or_at<F>(
&self,
below_idx: NodeIndex,
node_depth: usize,
criteria: F,
) -> Option<NodeIndex>
where
// Takes the node itself and its depth
F: Copy + Fn(PairNode, usize) -> bool,
{
let find_from_child = |child_idx| {
let node = self
.graph
.node_weight(child_idx)
.expect("got a child that didn't exist in the graph");
// if let PairNode::Leaf(_) = node {
// return None;
// }
let next_depth = node_depth + 1;
// println!(
// "{:?} (id={:?}) @ {} => {}",
// node, child_idx, node_depth, next_depth
// );
self.find_node_to_reduce_below_or_at(child_idx, next_depth, criteria)
};
let below_type = self.graph.node_weight(below_idx).unwrap();
let left_child = self.get_child(below_idx, Direction::Left);
let right_child = self.get_child(below_idx, Direction::Right);
let left_candidate = left_child.and_then(find_from_child);
let right_candidate = right_child.and_then(find_from_child);
if left_candidate.is_some() {
// dbg!(self.graph.node_weight(
// self.get_child(left_candidate.unwrap(), Direction::Left)
// .unwrap()
// ));
left_candidate
} else if right_candidate.is_some() {
right_candidate
} else if criteria(*below_type, node_depth) {
Some(below_idx)
} else {
None
}
}
fn get_parent(&self, node_idx: NodeIndex) -> Option<NodeIndex> {
// There must be zero or one by construction of the graph
self.graph.neighbors(node_idx).find(|&neighbor_idx| {
// This must exist by the fact that we've been returned a neighbor
let edge = self.graph.find_edge(node_idx, neighbor_idx).unwrap();
let edge_type = self.graph.edge_weight(edge).unwrap();
matches!(edge_type, EdgeType::Parent)
})
}
fn get_child(&self, node_idx: NodeIndex, direction: Direction) -> Option<NodeIndex> {
// There should only be one or zero in the iterator, by the construction of the graph.
self.graph.neighbors(node_idx).find(|&neighbor_idx| {
// This must exist by the fact that we've been returned a neighbor
let edge = self.graph.find_edge(node_idx, neighbor_idx).unwrap();
let edge_type = self.graph.edge_weight(edge).unwrap();
if let EdgeType::Child(child_direction) = edge_type {
mem::discriminant(child_direction) == mem::discriminant(&direction)
} else {
false
}
})
}
fn explode_in_relative_direction(
&mut self,
to_explode: NodeIndex,
direction: Direction,
) -> bool {
let explode_value = self
.graph
.node_weight(self.get_child(to_explode, direction).unwrap())
.map(|node| {
if let PairNode::Leaf(n) = node {
*n
} else {
// TODO: Probably shouldn't panic but I don't want to make errors right now
panic!("got a non-leaf when looking for explode node");
}
})
.unwrap();
println!("Exploding: {:?} (id={:?})", explode_value, to_explode);
let relative_direction_node_candidate =
self.get_leaf_in_relative_direction(to_explode, direction);
if let Some(relative_direction_node_idx) = relative_direction_node_candidate {
if relative_direction_node_idx == to_explode {
return false;
}
let relative_direction_node = self
.graph
.node_weight_mut(relative_direction_node_idx)
.unwrap();
// println!("{:?} {:?}", relative_direction_node_idx, to_explode);
if let PairNode::Leaf(n) = relative_direction_node {
println!(
"{:?}: {} (id={:?})",
direction, *n, relative_direction_node_idx
);
*n += explode_value;
} else {
panic!("got a non-leaf from directional leaf lookup");
}
true
} else {
false
}
}
fn split_node(&mut self, node_idx: NodeIndex) {
// TODO: Probably shouldn't panic but I don't want to write errors right now
let node = self.graph.node_weight_mut(node_idx).unwrap();
let n = if let PairNode::Leaf(n) = node {
*n
} else {
// TODO: _definitely_ shouldn't panic but I don't want to write errors right now
panic!("cannot explode non-leaf")
};
let (left_value, right_value) = get_split_values(n);
*node = PairNode::PairRoot;
self.insert_leaf(left_value, Direction::Left, node_idx);
self.insert_leaf(right_value, Direction::Right, node_idx);
}
fn get_leaf_in_relative_direction(
&self,
node_idx: NodeIndex,
direction: Direction,
) -> Option<NodeIndex> {
let mut prev_cursor = node_idx;
let mut cursor = self.get_parent(node_idx)?;
let have_found_root_like_node = |prev_cursor, cursor| {
let cursor_left_child = self.get_child(cursor, Direction::Left).unwrap();
let cursor_right_child = self.get_child(cursor, Direction::Right).unwrap();
let cursor_left_child_node = self.graph.node_weight(cursor_left_child).unwrap();
let cursor_right_child_node = self.graph.node_weight(cursor_right_child).unwrap();
match direction {
Direction::Right => {
if cursor_right_child == prev_cursor {
return false;
}
}
Direction::Left => {
if cursor_left_child == prev_cursor {
return false;
}
}
}
matches!(cursor_left_child_node, PairNode::PairRoot)
&& matches!(cursor_right_child_node, PairNode::PairRoot)
};
while !have_found_root_like_node(prev_cursor, cursor) {
if let Some(directional_child) = self.get_child(cursor, direction) {
let directional_child_node = self.graph.node_weight(directional_child).unwrap();
if directional_child != cursor
&& matches!(directional_child_node, PairNode::Leaf(_))
{
return Some(directional_child);
}
}
prev_cursor = cursor;
cursor = self.get_parent(cursor)?;
}
// once we hit the "root", we need to start going downwards by one level, and descend as far as possible
// in the opposite direction.
let flip_around_node = self.get_child(cursor, direction)?;
if flip_around_node == prev_cursor {
return None;
}
cursor = flip_around_node;
loop {
let child_candidate = self.get_child(cursor, direction.get_other());
if let Some(child) = child_candidate {
cursor = child;
} else {
return Some(cursor);
}
}
}
fn debug_tree(&self, formatter: &mut Formatter<'_>, node_idx: NodeIndex) -> fmt::Result {
let node_candidate = self.graph.node_weight(node_idx);
if node_candidate.is_none() {
return Err(fmt::Error);
}
let node = node_candidate.unwrap();
match node {
PairNode::Leaf(n) => write!(formatter, "{}", n)?,
PairNode::PairRoot => {
let left_node_idx_candidate = self.get_child(node_idx, Direction::Left);
let right_node_idx_candidate = self.get_child(node_idx, Direction::Right);
if left_node_idx_candidate.is_none() || left_node_idx_candidate.is_none() {
return Err(fmt::Error);
}
let left_node_idx = left_node_idx_candidate.unwrap();
let right_node_idx = right_node_idx_candidate.unwrap();
write!(formatter, "[")?;
self.debug_tree(formatter, left_node_idx)?;
write!(formatter, ",")?;
self.debug_tree(formatter, right_node_idx)?;
write!(formatter, "]")?;
}
};
Ok(())
}
}
impl Debug for ProblemTree {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
self.debug_tree(formatter, self.root_idx)
}
}
fn get_split_values(n: u32) -> (u32, u32) {
let left = n / 2;
let right = if n % 2 == 0 { n / 2 } else { n / 2 + 1 };
println!("Splitting: {} => {}, {}", n, left, right);
(left, right)
}
fn parse_snailfish_problem_leaf(chunk: &str) -> IResult<&str, InputPair> {
let (remaining, n) = map_res(digit1, str::parse)(chunk)?;
Ok((remaining, InputPair::Leaf(n)))
}
fn parse_snailfish_problem_pair(chunk: &str) -> IResult<&str, InputPair> {
let (remaining, (pair1, pair2)) = delimited(
char('['),
separated_pair(
alt((parse_snailfish_problem_pair, parse_snailfish_problem_leaf)),
char(','),
alt((parse_snailfish_problem_pair, parse_snailfish_problem_leaf)),
),
char(']'),
)(chunk)?;
let res_pair = InputPair::Pair(Box::new(pair1), Box::new(pair2));
Ok((remaining, res_pair))
}
fn parse_snailfish_problem(input: &str) -> IResult<&str, InputPair> {
terminated(parse_snailfish_problem_pair, eof)(input)
}
fn part1(input_pairs: &[InputPair]) -> u32 {
let mut problem_tree = if let InputPair::Pair(left, right) = &input_pairs[0] {
ProblemTree::build(&*left, &*right)
} else {
panic!("input pair should be a pair");
};
problem_tree.reduce();
for input_pair in &input_pairs[1..] {
problem_tree.insert_root_sibling_input_pair(input_pair, Direction::Right);
problem_tree.reduce();
dbg!(&problem_tree);
// print_tree(&problem_tree);
// std::io::stdin().read_line(&mut String::new());
}
// print_tree(&problem_tree);
dbg!(&problem_tree);
problem_tree.magnitude()
}
fn print_tree(problem_tree: &ProblemTree) {
println!("{:?}", Dot::with_config(&problem_tree.graph, &[]));
// let mut to_visit = vec![(0, problem_tree.root_idx)];
// while let Some((indentation, idx)) = to_visit.pop() {
// let node = problem_tree.graph.node_weight(idx).unwrap();
// match node {
// PairNode::Leaf(n) => println!(
// "{}{}",
// (0..=indentation).map(|_| " ").collect::<String>(),
// n
// ),
// PairNode::PairRoot => {
// for neighbor in problem_tree.graph.neighbors(idx) {
// let edge_idx = problem_tree.graph.find_edge(idx, neighbor).unwrap();
// let edge_type = problem_tree.graph.edge_weight(edge_idx).unwrap();
// if let EdgeType::Child(_) = edge_type {
// to_visit.push((indentation + 1, neighbor));
// }
// }
// }
// }
// }
}
fn main() {
let input_file_name = env::args().nth(1).expect("No input filename specified");
let input_file = File::open(input_file_name).expect("Could not open input file");
let all_pairs = BufReader::new(input_file)
.lines()
.map(|res| res.expect("Failed to read line"))
.filter_map(|line| {
if line.is_empty() {
return None;
}
let (_, input_pair) =
parse_snailfish_problem(&line).expect("Failed to parse input line");
Some(input_pair)
})
.collect::<Vec<_>>();
// .explode(|left, right| InputPair::Pair(Box::new(left), Box::new(right)))
// .expect("did not find input problem");
println!("Part 1: {}", part1(&all_pairs));
}