day22: problem 2 solution.

no help but annoyingly i misread the hint about low/high and didn't submit my *correct* answer for over an hour while I bug hunted!
This commit is contained in:
Keenan Tims 2023-12-22 03:45:29 -08:00
parent 8495969877
commit dd91259fe2
Signed by: ktims
GPG Key ID: 11230674D69038D4
4 changed files with 125 additions and 1372 deletions

39
22/Cargo.lock generated
View File

@ -14,6 +14,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"itertools", "itertools",
"ndarray", "ndarray",
"petgraph",
] ]
[[package]] [[package]]
@ -22,6 +23,34 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.0" version = "0.12.0"
@ -82,6 +111,16 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "petgraph"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "rawpointer" name = "rawpointer"
version = "0.2.1" version = "0.2.1"

View File

@ -8,3 +8,4 @@ edition = "2021"
[dependencies] [dependencies]
itertools = "0.12.0" itertools = "0.12.0"
ndarray = "0.15.6" ndarray = "0.15.6"
petgraph = "0.6.4"

1300
22/input

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
use itertools::Itertools; use itertools::Itertools;
use ndarray::prelude::*; use ndarray::prelude::*;
use std::borrow::BorrowMut; use petgraph::prelude::*;
use std::collections::{BinaryHeap, HashMap, VecDeque}; use petgraph::visit::{IntoNodeReferences, Walker};
use std::collections::BinaryHeap;
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, Lines}; use std::io::{BufRead, BufReader, Lines};
@ -67,16 +68,6 @@ impl std::fmt::Debug for BrickBlock {
} }
impl BrickBlock { impl BrickBlock {
fn map_remove(&self, mut map: BlockMap) -> BlockMap {
// loop over the (inclusive) bounding coordinates and remove them all from the map
map.slice_mut(s![
std::cmp::min(self.c1.x, self.c2.x)..std::cmp::max(self.c1.x, self.c2.x) + 1,
std::cmp::min(self.c1.y, self.c2.y)..std::cmp::max(self.c1.y, self.c2.y) + 1,
std::cmp::min(self.c1.z, self.c2.z)..std::cmp::max(self.c1.z, self.c2.z) + 1
])
.fill(None);
map
}
fn map_into(&self, mut map: BlockMap) -> BlockMap { fn map_into(&self, mut map: BlockMap) -> BlockMap {
// loop over the (inclusive) bounding coordinates and add them all to the map // loop over the (inclusive) bounding coordinates and add them all to the map
map.slice_mut(s![ map.slice_mut(s![
@ -137,6 +128,7 @@ struct BlockPile {
blocks: Vec<BrickBlock>, blocks: Vec<BrickBlock>,
block_map: Array3<MapTile>, block_map: Array3<MapTile>,
bounds: (usize, usize, usize), bounds: (usize, usize, usize),
graph: Graph<BrickBlock, (), Directed, usize>,
} }
impl BlockPile { impl BlockPile {
@ -172,31 +164,26 @@ impl BlockPile {
block.top_z_plane() + 1..std::cmp::min(block.top_z_plane() + 2, self.bounds.2) block.top_z_plane() + 1..std::cmp::min(block.top_z_plane() + 2, self.bounds.2)
]); ]);
directly_above.iter().filter_map(|v| v.clone()).dedup().collect() directly_above.iter().filter_map(|v| v.clone()).unique().collect()
} }
fn supported_by(&self, block: &BrickBlock) -> usize { fn supported_by(&self, block: &BrickBlock) -> usize {
println!("{:?} is supported by:", block);
let z_plane = std::cmp::min(block.c1.z, block.c2.z); let z_plane = std::cmp::min(block.c1.z, block.c2.z);
println!(" z plane: {}", z_plane);
// get the slice of tiles below us // get the slice of tiles below us
let directly_below = self.block_map.slice(s![ let directly_below = self.block_map.slice(s![
block.bottom_x_plane()..block.top_x_plane() + 1, block.bottom_x_plane()..block.top_x_plane() + 1,
block.bottom_y_plane()..block.top_y_plane() + 1, block.bottom_y_plane()..block.top_y_plane() + 1,
z_plane - 1..z_plane // don't include our own plane z_plane - 1..z_plane // the layer below
]); ]);
println!( directly_below.iter().filter_map(|v| v.clone()).unique().count()
" {} directly below {:?}",
directly_below.iter().filter_map(|v| v.clone()).dedup().count(),
block
);
directly_below.iter().filter_map(|v| v.clone()).dedup().count()
} }
fn blocks_above_will_move_if_we_are_gone(&mut self, block: &BrickBlock) -> bool { fn blocks_above_will_move_if_we_are_gone(&mut self, block: &BrickBlock) -> bool {
println!("checking directly above block {:?}", block);
self.blocks_directly_above(&block) self.blocks_directly_above(&block)
.iter() .iter()
.map(|b| self.supported_by(b)) .map(|b| self.supported_by(b))
.any(|b| b == 1) // 0 can happen at the origin, and an empty iterator (falsy) will happen at the top .any(|b| b == 1) // block we support will move if we are their only support
}
fn blocks_supported_by_at_all(&self, block: &BrickBlock) -> Vec<BrickBlock> {
self.blocks_directly_above(&block).iter().map(|b| b.clone()).collect()
} }
/// Find the plane of the first block directly below us /// Find the plane of the first block directly below us
fn supporting_plane(&self, block: &BrickBlock) -> Option<usize> { fn supporting_plane(&self, block: &BrickBlock) -> Option<usize> {
@ -222,51 +209,38 @@ impl BlockPile {
} }
fn drop_blocks(&mut self) { fn drop_blocks(&mut self) {
// VecDeque doesn't sort and Vec isn't convenient for pushback and popfront, so eh... use a heap.
let mut blocks_to_move = BinaryHeap::from(self.blocks.clone()); let mut blocks_to_move = BinaryHeap::from(self.blocks.clone());
while let Some(mut block) = blocks_to_move.pop() { while let Some(mut block) = blocks_to_move.pop() {
match self.supporting_plane(&block) { let z_move = match self.supporting_plane(&block) {
Some(z) if z + 1 != block.bottom_z_plane() => { Some(z) if z + 1 != block.bottom_z_plane() => block.bottom_z_plane() - (z + 1),
// we can move down to this position None if block.bottom_z_plane() != 1 => block.bottom_z_plane() - 1,
// set old position to None
self.remove_block(&block);
// set new positions
let z_height = block.c1.z.abs_diff(block.c2.z);
block.c1.z = z + 1;
block.c2.z = z + 1 + z_height;
self.add_block(&block);
// queue the block again
blocks_to_move.push(block);
}
None if block.bottom_z_plane() != 1 => {
// we can move down to this position
// set old position to None
let z = 1;
self.remove_block(&block);
// set new positions
let z_height = block.c1.z.abs_diff(block.c2.z);
block.c1.z = z;
block.c2.z = z + z_height;
self.add_block(&block);
// queue the block again
blocks_to_move.push(block);
}
_ => { _ => {
continue; continue;
} // we are in position already with nothing below us } // we are in position already with nothing below us
} };
self.remove_block(&block);
block.c1.z -= z_move;
block.c2.z -= z_move;
self.add_block(&block);
blocks_to_move.push(block);
} }
} }
fn can_move(&self, block: &BrickBlock) -> bool { fn build_graph(&mut self) {
match self.supporting_plane(&block) { self.blocks.sort_by_key(|b| b.bottom_z_plane());
Some(z) if z + 1 != block.bottom_z_plane() => { for b in 0..self.blocks.len() {
return true; self.graph.add_node(self.blocks[b].clone());
}
for b in 0..self.blocks.len() {
let block = &self.blocks[b];
let depends_on_us = self.blocks_supported_by_at_all(block);
for dependent in depends_on_us {
self.graph.add_edge(
b.into(),
self.blocks.iter().position(|b| b == &dependent).unwrap().into(),
(),
);
} }
None if block.bottom_z_plane() != 1 => {
return true;
}
_ => {
return false;
} // we are in position already with nothing below us
} }
} }
} }
@ -277,6 +251,7 @@ impl<T: BufRead> From<Lines<T>> for BlockPile {
blocks: lines.map(|line| BrickBlock::from(line.unwrap().as_str())).collect(), blocks: lines.map(|line| BrickBlock::from(line.unwrap().as_str())).collect(),
block_map: Array3::from_elem([0, 0, 0], None), block_map: Array3::from_elem([0, 0, 0], None),
bounds: (0, 0, 0), bounds: (0, 0, 0),
graph: Graph::default(),
}; };
for block in &new.blocks { for block in &new.blocks {
@ -338,25 +313,63 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
println!("{}", pile); println!("{}", pile);
let blocks = pile.blocks.clone(); let blocks = pile.blocks.clone();
let mut must_keep: Vec<_> = blocks let removable: Vec<_> = blocks
.iter()
.filter(|b| pile.blocks_above_will_move_if_we_are_gone(*b))
.collect();
let mut removable: Vec<_> = blocks
.iter() .iter()
.filter(|b| !pile.blocks_above_will_move_if_we_are_gone(*b)) .filter(|b| !pile.blocks_above_will_move_if_we_are_gone(*b))
.collect(); .collect();
removable.sort_by(|a, b| a.bottom_z_plane().cmp(&b.bottom_z_plane()));
must_keep.sort_by(|a, b| a.bottom_z_plane().cmp(&b.bottom_z_plane()));
println!("remove: {:?}", removable);
removable.len() as u64 removable.len() as u64
} }
// PROBLEM 2 solution // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0 let mut pile = BlockPile::from(input);
pile.drop_blocks();
pile.build_graph();
println!("{}", pile);
println!("block 0 {:?}", pile.blocks[0]);
let mut accum = 0;
let fixed_nodes = pile
.graph
.node_references()
.filter(|(_idx, b)| b.bottom_z_plane() == 1)
.map(|(idx, _b)| idx)
.collect_vec();
for node in pile.graph.node_indices() {
// remove links to node's neighbors
let dependents = pile.graph.neighbors(node).collect_vec();
let edges = pile.graph.edges(node).map(|v| v.id()).collect_vec();
for edge in edges {
pile.graph.remove_edge(edge);
}
// find how many nodes are reachable from z = 1 - these won't move
let safe_blocks = fixed_nodes
.iter()
.flat_map(|origin| {
Bfs::new(&pile.graph, *origin)
.iter(&pile.graph)
.map(|n| pile.graph[n].clone())
})
.unique()
.count();
// we are looking for the nodes that *will* disintegrate
println!(
"From {}, {} safe, {} disintegrate",
node.index(),
safe_blocks,
pile.graph.node_count() - safe_blocks
);
accum += pile.graph.node_count() - safe_blocks;
// put the graph back how it was
for neigh in dependents {
pile.graph.add_edge(node, neigh, ());
}
}
println!("blocks: {} nodes: {}", pile.blocks.len(), pile.graph.node_count());
accum as u64
} }
#[cfg(test)] #[cfg(test)]
@ -381,6 +394,6 @@ mod tests {
#[test] #[test]
fn problem2_example() { fn problem2_example() {
let c = Cursor::new(EXAMPLE); let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0); assert_eq!(problem2(c.lines()), 7);
} }
} }