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:
parent
8495969877
commit
dd91259fe2
39
22/Cargo.lock
generated
39
22/Cargo.lock
generated
@ -14,6 +14,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"ndarray",
|
||||
"petgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -22,6 +23,34 @@ version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "itertools"
|
||||
version = "0.12.0"
|
||||
@ -82,6 +111,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
|
@ -8,3 +8,4 @@ edition = "2021"
|
||||
[dependencies]
|
||||
itertools = "0.12.0"
|
||||
ndarray = "0.15.6"
|
||||
petgraph = "0.6.4"
|
||||
|
155
22/src/main.rs
155
22/src/main.rs
@ -1,7 +1,8 @@
|
||||
use itertools::Itertools;
|
||||
use ndarray::prelude::*;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::collections::{BinaryHeap, HashMap, VecDeque};
|
||||
use petgraph::prelude::*;
|
||||
use petgraph::visit::{IntoNodeReferences, Walker};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
@ -67,16 +68,6 @@ impl std::fmt::Debug for 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 {
|
||||
// loop over the (inclusive) bounding coordinates and add them all to the map
|
||||
map.slice_mut(s![
|
||||
@ -137,6 +128,7 @@ struct BlockPile {
|
||||
blocks: Vec<BrickBlock>,
|
||||
block_map: Array3<MapTile>,
|
||||
bounds: (usize, usize, usize),
|
||||
graph: Graph<BrickBlock, (), Directed, usize>,
|
||||
}
|
||||
|
||||
impl BlockPile {
|
||||
@ -172,31 +164,26 @@ impl BlockPile {
|
||||
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 {
|
||||
println!("{:?} is supported by:", block);
|
||||
let z_plane = std::cmp::min(block.c1.z, block.c2.z);
|
||||
println!(" z plane: {}", z_plane);
|
||||
// get the slice of tiles below us
|
||||
let directly_below = self.block_map.slice(s![
|
||||
block.bottom_x_plane()..block.top_x_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 {:?}",
|
||||
directly_below.iter().filter_map(|v| v.clone()).dedup().count(),
|
||||
block
|
||||
);
|
||||
directly_below.iter().filter_map(|v| v.clone()).dedup().count()
|
||||
directly_below.iter().filter_map(|v| v.clone()).unique().count()
|
||||
}
|
||||
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)
|
||||
.iter()
|
||||
.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
|
||||
fn supporting_plane(&self, block: &BrickBlock) -> Option<usize> {
|
||||
@ -222,51 +209,38 @@ impl BlockPile {
|
||||
}
|
||||
|
||||
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());
|
||||
while let Some(mut block) = blocks_to_move.pop() {
|
||||
match self.supporting_plane(&block) {
|
||||
Some(z) if z + 1 != block.bottom_z_plane() => {
|
||||
// we can move down to this position
|
||||
// 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);
|
||||
}
|
||||
let z_move = match self.supporting_plane(&block) {
|
||||
Some(z) if z + 1 != block.bottom_z_plane() => block.bottom_z_plane() - (z + 1),
|
||||
None if block.bottom_z_plane() != 1 => block.bottom_z_plane() - 1,
|
||||
_ => {
|
||||
continue;
|
||||
} // 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 build_graph(&mut self) {
|
||||
self.blocks.sort_by_key(|b| b.bottom_z_plane());
|
||||
for b in 0..self.blocks.len() {
|
||||
self.graph.add_node(self.blocks[b].clone());
|
||||
}
|
||||
fn can_move(&self, block: &BrickBlock) -> bool {
|
||||
match self.supporting_plane(&block) {
|
||||
Some(z) if z + 1 != block.bottom_z_plane() => {
|
||||
return true;
|
||||
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(),
|
||||
block_map: Array3::from_elem([0, 0, 0], None),
|
||||
bounds: (0, 0, 0),
|
||||
graph: Graph::default(),
|
||||
};
|
||||
|
||||
for block in &new.blocks {
|
||||
@ -338,25 +313,63 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
println!("{}", pile);
|
||||
|
||||
let blocks = pile.blocks.clone();
|
||||
let mut must_keep: Vec<_> = blocks
|
||||
.iter()
|
||||
.filter(|b| pile.blocks_above_will_move_if_we_are_gone(*b))
|
||||
.collect();
|
||||
let mut removable: Vec<_> = blocks
|
||||
let removable: Vec<_> = blocks
|
||||
.iter()
|
||||
.filter(|b| !pile.blocks_above_will_move_if_we_are_gone(*b))
|
||||
.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
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
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)]
|
||||
@ -381,6 +394,6 @@ mod tests {
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem2(c.lines()), 0);
|
||||
assert_eq!(problem2(c.lines()), 7);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user