221 lines
5.7 KiB
Rust
221 lines
5.7 KiB
Rust
advent_of_code::solution!(22);
|
|
|
|
use aoc_parse::{parser, prelude::*};
|
|
use std::collections::{HashMap, HashSet, VecDeque};
|
|
|
|
#[derive(Clone, Copy, Debug, PartialOrd, Ord, Eq, PartialEq)]
|
|
struct Coord {
|
|
z: usize,
|
|
x: usize,
|
|
y: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq)]
|
|
struct Block {
|
|
id: usize,
|
|
one: Coord,
|
|
two: Coord,
|
|
}
|
|
|
|
impl Block {
|
|
fn intersect_xy(&self, other: &Self) -> bool {
|
|
self.one.x <= other.two.x
|
|
&& other.one.x <= self.two.x
|
|
&& self.one.y <= other.two.y
|
|
&& other.one.y <= self.two.y
|
|
}
|
|
|
|
fn below(&self, other: &Self) -> bool {
|
|
self.intersect_xy(other) && self.two.z == other.one.z - 1
|
|
}
|
|
|
|
fn above(&self, other: &Self) -> bool {
|
|
self.intersect_xy(other) && self.one.z == other.two.z + 1
|
|
}
|
|
}
|
|
|
|
fn parse(input: &str) -> Vec<Block> {
|
|
let p = parser!(lines(usize "," usize "," usize "~" usize "," usize "," usize));
|
|
let mut blocks = p
|
|
.parse(input)
|
|
.unwrap()
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, r)| Block {
|
|
id: index + 1,
|
|
one: Coord {
|
|
z: r.2,
|
|
x: r.0,
|
|
y: r.1,
|
|
},
|
|
two: Coord {
|
|
z: r.5,
|
|
x: r.3,
|
|
y: r.4,
|
|
},
|
|
})
|
|
.collect::<Vec<_>>();
|
|
blocks.sort_by_key(|b| b.one);
|
|
blocks
|
|
}
|
|
|
|
fn drop_blocks(blocks: &[Block]) -> Vec<Block> {
|
|
let mut btm: Vec<Block> = Vec::with_capacity(blocks.len());
|
|
for b in blocks.iter() {
|
|
let mut block = b.clone();
|
|
for _i in 1..b.one.z {
|
|
if let Some(_below) = btm.iter().find(|d| d.below(&block)) {
|
|
break;
|
|
}
|
|
block.one.z -= 1;
|
|
block.two.z -= 1;
|
|
}
|
|
btm.push(block);
|
|
}
|
|
btm
|
|
}
|
|
|
|
pub fn part_one(input_str: &str) -> Option<usize> {
|
|
let input = parse(input_str);
|
|
let btm = drop_blocks(&input);
|
|
let mut b: HashMap<usize, Vec<usize>> = HashMap::new();
|
|
for outside in btm.iter() {
|
|
let mut below: Vec<usize> = vec![];
|
|
for inside in btm.iter() {
|
|
if inside.below(&outside) {
|
|
below.push(inside.id);
|
|
}
|
|
}
|
|
b.insert(outside.id, below);
|
|
}
|
|
let mut s: HashSet<usize> = HashSet::new();
|
|
for id in 1..=btm.len() {
|
|
if let Some(below) = b.get(&id) {
|
|
if below.len() == 1 {
|
|
for &x in below {
|
|
s.insert(x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(btm.len() - s.len())
|
|
}
|
|
|
|
pub fn part_two(input_str: &str) -> Option<usize> {
|
|
let input = parse(input_str);
|
|
let btm = drop_blocks(&input);
|
|
let mut a: HashMap<usize, Vec<usize>> = HashMap::new();
|
|
for outside in btm.iter() {
|
|
let mut above: Vec<usize> = vec![];
|
|
for inside in btm.iter() {
|
|
if inside.above(&outside) {
|
|
above.push(inside.id);
|
|
}
|
|
}
|
|
a.insert(outside.id, above);
|
|
}
|
|
let mut result = 0;
|
|
let mut v: HashSet<usize> = HashSet::new();
|
|
let mut p: VecDeque<usize> = VecDeque::new();
|
|
for b in btm.iter() {
|
|
if b.one.z == 1 {
|
|
v.insert(b.id);
|
|
p.push_back(b.id);
|
|
}
|
|
}
|
|
for id in 1..=btm.len() {
|
|
let mut visited = v.clone();
|
|
let mut path = p.clone();
|
|
while let Some(next) = path.pop_front() {
|
|
if next == id {
|
|
continue;
|
|
}
|
|
if let Some(above) = a.get(&next) {
|
|
for &above_id in above {
|
|
if !visited.contains(&above_id) {
|
|
visited.insert(above_id);
|
|
path.push_back(above_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result += btm.len() - visited.len();
|
|
}
|
|
Some(result)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_part_one() {
|
|
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(5));
|
|
}
|
|
|
|
#[test]
|
|
fn test_part_two() {
|
|
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(7));
|
|
}
|
|
|
|
fn perform_test_intersect(
|
|
one: &(usize, usize, usize, usize, usize, usize),
|
|
two: &(usize, usize, usize, usize, usize, usize),
|
|
res: bool,
|
|
) {
|
|
let b1 = Block {
|
|
id: 1,
|
|
one: Coord {
|
|
x: one.0,
|
|
y: one.1,
|
|
z: one.2,
|
|
},
|
|
two: Coord {
|
|
x: one.3,
|
|
y: one.4,
|
|
z: one.5,
|
|
},
|
|
};
|
|
let b2 = Block {
|
|
id: 2,
|
|
one: Coord {
|
|
x: two.0,
|
|
y: two.1,
|
|
z: two.2,
|
|
},
|
|
two: Coord {
|
|
x: two.3,
|
|
y: two.4,
|
|
z: two.5,
|
|
},
|
|
};
|
|
assert_eq!(b1.intersect_xy(&b2), res);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intersect_xy() {
|
|
let false_coords = [
|
|
[(0, 0, 1, 0, 0, 1), (1, 0, 1, 4, 0, 1)],
|
|
[(0, 0, 1, 0, 0, 1), (0, 1, 1, 0, 4, 1)],
|
|
[(0, 0, 1, 10, 0, 1), (1, 1, 1, 1, 1, 1)],
|
|
[(0, 0, 1, 10, 0, 1), (1, 1, 1, 1, 10, 1)],
|
|
];
|
|
for row in false_coords.iter() {
|
|
let one = row[0];
|
|
let two = row[1];
|
|
perform_test_intersect(&one, &two, false);
|
|
}
|
|
let true_coords = [
|
|
[(0, 0, 1, 0, 0, 1), (0, 0, 1, 4, 0, 1)],
|
|
[(0, 0, 1, 10, 0, 1), (5, 0, 1, 5, 5, 1)],
|
|
];
|
|
for row in true_coords.iter() {
|
|
let one = row[0];
|
|
let two = row[1];
|
|
perform_test_intersect(&one, &two, true);
|
|
}
|
|
}
|
|
}
|