Compare commits
No commits in common. "dd91259fe2fde59d36117e1257740dc9dfa1cedf" and "190fc928426384117ea7444da78dcbbab7226808" have entirely different histories.
dd91259fe2
...
190fc92842
128
22/Cargo.lock
generated
128
22/Cargo.lock
generated
@ -1,128 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "day22"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"itertools",
|
|
||||||
"ndarray",
|
|
||||||
"petgraph",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
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"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matrixmultiply"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"rawpointer",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ndarray"
|
|
||||||
version = "0.15.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
|
|
||||||
dependencies = [
|
|
||||||
"matrixmultiply",
|
|
||||||
"num-complex",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
"rawpointer",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-complex"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.45"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
|
||||||
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"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "day22"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
itertools = "0.12.0"
|
|
||||||
ndarray = "0.15.6"
|
|
||||||
petgraph = "0.6.4"
|
|
399
22/src/main.rs
399
22/src/main.rs
@ -1,399 +0,0 @@
|
|||||||
use itertools::Itertools;
|
|
||||||
use ndarray::prelude::*;
|
|
||||||
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};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
// BOILERPLATE
|
|
||||||
type InputIter = Lines<BufReader<File>>;
|
|
||||||
|
|
||||||
fn get_input() -> InputIter {
|
|
||||||
let f = File::open("input").unwrap();
|
|
||||||
let br = BufReader::new(f);
|
|
||||||
br.lines()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let start = Instant::now();
|
|
||||||
let ans1 = problem1(get_input());
|
|
||||||
let duration = start.elapsed();
|
|
||||||
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let ans2 = problem2(get_input());
|
|
||||||
let duration = start.elapsed();
|
|
||||||
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
|
|
||||||
}
|
|
||||||
|
|
||||||
// PARSE
|
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
|
||||||
struct Coord {
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
z: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Coord {
|
|
||||||
type Err = Box<dyn std::error::Error>;
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
|
||||||
let (x, y, z) = value.split(',').next_tuple().unwrap();
|
|
||||||
Ok(Self {
|
|
||||||
x: x.parse()?,
|
|
||||||
y: y.parse()?,
|
|
||||||
z: z.parse()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
|
||||||
struct BrickBlock {
|
|
||||||
c1: Coord,
|
|
||||||
c2: Coord,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for BrickBlock {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"[{},{},{} - {},{},{}]",
|
|
||||||
self.c1.x, self.c1.y, self.c1.z, self.c2.x, self.c2.y, self.c2.z
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BrickBlock {
|
|
||||||
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![
|
|
||||||
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(Some(self.to_owned()));
|
|
||||||
map
|
|
||||||
}
|
|
||||||
fn bottom_z_plane(&self) -> usize {
|
|
||||||
std::cmp::min(self.c1.z, self.c2.z)
|
|
||||||
}
|
|
||||||
fn top_z_plane(&self) -> usize {
|
|
||||||
std::cmp::max(self.c1.z, self.c2.z)
|
|
||||||
}
|
|
||||||
fn bottom_x_plane(&self) -> usize {
|
|
||||||
std::cmp::min(self.c1.x, self.c2.x)
|
|
||||||
}
|
|
||||||
fn top_x_plane(&self) -> usize {
|
|
||||||
std::cmp::max(self.c1.x, self.c2.x)
|
|
||||||
}
|
|
||||||
fn bottom_y_plane(&self) -> usize {
|
|
||||||
std::cmp::min(self.c1.y, self.c2.y)
|
|
||||||
}
|
|
||||||
fn top_y_plane(&self) -> usize {
|
|
||||||
std::cmp::max(self.c1.y, self.c2.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for BrickBlock {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
let (c1, c2) = value.split_once('~').unwrap();
|
|
||||||
Self {
|
|
||||||
c1: c1.parse().unwrap(),
|
|
||||||
c2: c2.parse().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for BrickBlock {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(other.bottom_z_plane().cmp(&self.bottom_z_plane()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note this is a reversed ordering
|
|
||||||
impl Ord for BrickBlock {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.bottom_z_plane().cmp(&other.bottom_z_plane())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlockMap = Array3<MapTile>;
|
|
||||||
type MapTile = Option<BrickBlock>;
|
|
||||||
|
|
||||||
struct BlockPile {
|
|
||||||
blocks: Vec<BrickBlock>,
|
|
||||||
block_map: Array3<MapTile>,
|
|
||||||
bounds: (usize, usize, usize),
|
|
||||||
graph: Graph<BrickBlock, (), Directed, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockPile {
|
|
||||||
fn remove_block(&mut self, block: &BrickBlock) {
|
|
||||||
// loop over the (inclusive) bounding coordinates and remove them all from the map
|
|
||||||
self.block_map
|
|
||||||
.slice_mut(s![
|
|
||||||
std::cmp::min(block.c1.x, block.c2.x)..std::cmp::max(block.c1.x, block.c2.x) + 1,
|
|
||||||
std::cmp::min(block.c1.y, block.c2.y)..std::cmp::max(block.c1.y, block.c2.y) + 1,
|
|
||||||
std::cmp::min(block.c1.z, block.c2.z)..std::cmp::max(block.c1.z, block.c2.z) + 1
|
|
||||||
])
|
|
||||||
.fill(None);
|
|
||||||
self.blocks.remove(self.blocks.iter().position(|b| b == block).unwrap());
|
|
||||||
}
|
|
||||||
fn add_block(&mut self, block: &BrickBlock) {
|
|
||||||
// loop over the (inclusive) bounding coordinates and remove them all from the map
|
|
||||||
self.block_map
|
|
||||||
.slice_mut(s![
|
|
||||||
std::cmp::min(block.c1.x, block.c2.x)..std::cmp::max(block.c1.x, block.c2.x) + 1,
|
|
||||||
std::cmp::min(block.c1.y, block.c2.y)..std::cmp::max(block.c1.y, block.c2.y) + 1,
|
|
||||||
std::cmp::min(block.c1.z, block.c2.z)..std::cmp::max(block.c1.z, block.c2.z) + 1
|
|
||||||
])
|
|
||||||
.fill(Some(block.to_owned()));
|
|
||||||
self.blocks.push(block.clone());
|
|
||||||
}
|
|
||||||
fn blocks_directly_above(&self, block: &BrickBlock) -> Vec<BrickBlock> {
|
|
||||||
// find the top plane of the block
|
|
||||||
// get the array range of all squares at z_plane + 1 within the bounds of x & y
|
|
||||||
// i think there is a built in way in ndarray to do this...
|
|
||||||
let directly_above = self.block_map.slice(s![
|
|
||||||
block.bottom_x_plane()..block.top_x_plane() + 1,
|
|
||||||
block.bottom_y_plane()..block.top_y_plane() + 1,
|
|
||||||
block.top_z_plane() + 1..std::cmp::min(block.top_z_plane() + 2, self.bounds.2)
|
|
||||||
]);
|
|
||||||
|
|
||||||
directly_above.iter().filter_map(|v| v.clone()).unique().collect()
|
|
||||||
}
|
|
||||||
fn supported_by(&self, block: &BrickBlock) -> usize {
|
|
||||||
let z_plane = std::cmp::min(block.c1.z, block.c2.z);
|
|
||||||
// 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 // the layer below
|
|
||||||
]);
|
|
||||||
directly_below.iter().filter_map(|v| v.clone()).unique().count()
|
|
||||||
}
|
|
||||||
fn blocks_above_will_move_if_we_are_gone(&mut self, block: &BrickBlock) -> bool {
|
|
||||||
self.blocks_directly_above(&block)
|
|
||||||
.iter()
|
|
||||||
.map(|b| self.supported_by(b))
|
|
||||||
.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> {
|
|
||||||
// find the bottom plane of ourselves
|
|
||||||
let z_plane = std::cmp::min(block.c1.z, block.c2.z);
|
|
||||||
// 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,
|
|
||||||
1..z_plane // don't include our own plane
|
|
||||||
]);
|
|
||||||
// find the highest z value
|
|
||||||
let block_below = directly_below
|
|
||||||
.indexed_iter()
|
|
||||||
.filter_map(|(idx, v)| if let Some(val) = v { Some((idx, val)) } else { None })
|
|
||||||
.max_by(|(_, a), (_, b)| a.top_z_plane().cmp(&b.top_z_plane()));
|
|
||||||
|
|
||||||
if let Some(block) = block_below {
|
|
||||||
Some(block.1.top_z_plane())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BufRead> From<Lines<T>> for BlockPile {
|
|
||||||
fn from(lines: Lines<T>) -> Self {
|
|
||||||
let mut new: BlockPile = Self {
|
|
||||||
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 {
|
|
||||||
new.bounds.0 = std::cmp::max(block.c1.x + 1, std::cmp::max(block.c2.y + 1, new.bounds.0));
|
|
||||||
new.bounds.1 = std::cmp::max(block.c1.y + 1, std::cmp::max(block.c2.y + 1, new.bounds.1));
|
|
||||||
new.bounds.2 = std::cmp::max(block.c1.z + 1, std::cmp::max(block.c2.z + 1, new.bounds.2));
|
|
||||||
}
|
|
||||||
let mut block_map = BlockMap::from_elem(new.bounds, None);
|
|
||||||
|
|
||||||
for block in &new.blocks {
|
|
||||||
block_map = block.map_into(block_map);
|
|
||||||
}
|
|
||||||
new.block_map = block_map;
|
|
||||||
|
|
||||||
new
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BlockPile {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
// XZ view
|
|
||||||
writeln!(f, "XZ: [{}, {}]", self.bounds.0, self.bounds.2)?;
|
|
||||||
for z in (0..self.bounds.2).rev() {
|
|
||||||
for x in 0..self.bounds.0 {
|
|
||||||
let y = self.block_map.slice(s![x..x + 1, .., z..z + 1]);
|
|
||||||
f.write_char(match y.iter().filter(|v| v.is_some()).count() {
|
|
||||||
0 => '.',
|
|
||||||
1 => '#',
|
|
||||||
_ => '?',
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
writeln!(f, " {}", z)?;
|
|
||||||
}
|
|
||||||
// YZ view
|
|
||||||
writeln!(f)?;
|
|
||||||
writeln!(f, "YZ: [{}, {}]", self.bounds.1, self.bounds.2)?;
|
|
||||||
for z in (0..self.bounds.2).rev() {
|
|
||||||
for y in 0..self.bounds.1 {
|
|
||||||
let x = self.block_map.slice(s![.., y..y + 1, z..z + 1]);
|
|
||||||
f.write_char(match x.iter().filter(|v| v.is_some()).count() {
|
|
||||||
0 => '.',
|
|
||||||
1 => '#',
|
|
||||||
_ => '?',
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
writeln!(f, " {}", z)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PROBLEM 1 solution
|
|
||||||
|
|
||||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
|
||||||
let mut pile = BlockPile::from(input);
|
|
||||||
println!("{}", pile);
|
|
||||||
println!("dropping blocks!");
|
|
||||||
pile.drop_blocks();
|
|
||||||
println!("{}", pile);
|
|
||||||
|
|
||||||
let blocks = pile.blocks.clone();
|
|
||||||
let removable: Vec<_> = blocks
|
|
||||||
.iter()
|
|
||||||
.filter(|b| !pile.blocks_above_will_move_if_we_are_gone(*b))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
removable.len() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
// PROBLEM 2 solution
|
|
||||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
|
||||||
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)]
|
|
||||||
mod tests {
|
|
||||||
use crate::*;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
const EXAMPLE: &str = &"1,0,1~1,2,1
|
|
||||||
0,0,2~2,0,2
|
|
||||||
0,2,3~2,2,3
|
|
||||||
0,0,4~0,2,4
|
|
||||||
2,0,5~2,2,5
|
|
||||||
0,1,6~2,1,6
|
|
||||||
1,1,8~1,1,9";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn problem1_example() {
|
|
||||||
let c = Cursor::new(EXAMPLE);
|
|
||||||
assert_eq!(problem1(c.lines()), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn problem2_example() {
|
|
||||||
let c = Cursor::new(EXAMPLE);
|
|
||||||
assert_eq!(problem2(c.lines()), 7);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user