Solve day18

This commit is contained in:
Malte Tammena 2022-12-18 14:06:58 +01:00
parent 23ac043d8e
commit be9627ae47
5 changed files with 2298 additions and 2 deletions

42
Cargo.lock generated
View file

@ -63,6 +63,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bitvec_simd"
version = "0.20.5"
@ -334,6 +346,9 @@ version = "0.1.0"
[[package]]
name = "day18"
version = "0.1.0"
dependencies = [
"bitvec",
]
[[package]]
name = "day19"
@ -386,6 +401,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.25"
@ -679,6 +700,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@ -799,6 +826,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
version = "1.1.3"
@ -988,3 +1021,12 @@ name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]

View file

@ -8,3 +8,4 @@ build = true
app = true
[dependencies]
bitvec = "1.0.1"

2025
day18/input Normal file

File diff suppressed because it is too large Load diff

13
day18/input.test Normal file
View file

@ -0,0 +1,13 @@
2,2,2
1,2,2
3,2,2
2,1,2
2,3,2
2,2,1
2,2,3
2,2,4
2,2,6
1,2,5
3,2,5
2,1,5
2,3,5

View file

@ -1,3 +1,218 @@
fn main() {
println!("Hello, world!");
#![feature(iter_next_chunk)]
use std::{
collections::HashSet,
fs::File,
io::{BufRead, BufReader},
};
use bitvec::{order::Lsb0, vec::BitVec};
#[derive(Debug, Default, PartialEq, Eq)]
enum SolvePuzzle {
First,
#[default]
Second,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
struct Point {
x: i16,
y: i16,
z: i16,
}
impl Point {
const fn new(x: i16, y: i16, z: i16) -> Point {
Self { x, y, z }
}
const fn convert_to_sides(self) -> impl IntoIterator<Item = Self> {
let Point { x, y, z } = self;
[
Point::new(2 * x - 1, 2 * y, 2 * z),
Point::new(2 * x + 1, 2 * y, 2 * z),
Point::new(2 * x, 2 * y - 1, 2 * z),
Point::new(2 * x, 2 * y + 1, 2 * z),
Point::new(2 * x, 2 * y, 2 * z - 1),
Point::new(2 * x, 2 * y, 2 * z + 1),
]
}
const fn make_cantor_proud(self) -> usize {
let Point { x, y, z } = self;
// Map Z to N
let x = if x < 0 {
(-2 * x) as usize - 1
} else {
2 * x as usize
};
let y = if y < 0 {
(-2 * y) as usize - 1
} else {
2 * y as usize
};
let z = if z < 0 {
(-2 * z) as usize - 1
} else {
2 * z as usize
};
// Reduce the first tuple
let xy = (x + y) * (x + y + 1) / 2 + y;
// Reduce!
(xy + z) * (xy + z + 1) / 2 + z
}
fn neighbors(&self) -> impl IntoIterator<Item = Self> {
let Point { x, y, z } = *self;
[
Point::new(x + 1, y, z),
Point::new(x - 1, y, z),
Point::new(x, y + 1, z),
Point::new(x, y - 1, z),
Point::new(x, y, z + 1),
Point::new(x, y, z - 1),
]
}
}
fn parse_triple<S: AsRef<str>>(line: S) -> Point {
let [x, y, z] = line
.as_ref()
.split(',')
.next_chunk()
.expect("Line contains three values");
Point {
x: x.parse().expect("X is a valid value"),
y: y.parse().expect("Y is a valid value"),
z: z.parse().expect("Z is a valid value"),
}
}
fn solve_first_puzzle<R: BufRead>(reader: R) -> usize {
reader
.lines()
.map(Result::unwrap)
.map(parse_triple)
.flat_map(Point::convert_to_sides)
.map(Point::make_cantor_proud)
.fold(BitVec::<usize, Lsb0>::new(), |mut vec, num| {
if num + 1 > vec.len() {
vec.resize(num + 1, false);
}
let bit = !vec[num];
vec.set(num, bit);
vec
})
.count_ones()
}
fn solve_second_puzzle<R: BufRead>(reader: R) -> usize {
let mut cubes: HashSet<_> = reader
.lines()
.map(Result::unwrap)
.map(parse_triple)
.collect();
let (x_min, x_max, y_min, y_max, z_min, z_max) = cubes.iter().fold(
(i16::MAX, i16::MIN, i16::MAX, i16::MIN, i16::MAX, i16::MIN),
|(x_min, x_max, y_min, y_max, z_min, z_max), Point { x, y, z }| {
(
x_min.min(*x),
x_max.max(*x),
y_min.min(*y),
y_max.max(*y),
z_min.min(*z),
z_max.max(*z),
)
},
);
let mut air = HashSet::new();
for x in x_min..=x_max {
for y in y_min..=y_max {
for z in z_min..=z_max {
let p = Point::new(x, y, z);
if !cubes.contains(&p) {
air.insert(p);
}
}
}
}
let mut reached: HashSet<Point> = HashSet::new();
let start = Point {
x: x_min,
y: y_min,
z: z_min,
};
flood_fill(&air, &mut reached, start);
air.difference(&reached).for_each(|point| {
cubes.insert(*point);
});
cubes
.into_iter()
.flat_map(Point::convert_to_sides)
.map(Point::make_cantor_proud)
.fold(BitVec::<usize, Lsb0>::new(), |mut vec, num| {
if num + 1 > vec.len() {
vec.resize(num + 1, false);
}
let bit = !vec[num];
vec.set(num, bit);
vec
})
.count_ones()
}
fn flood_fill(air: &HashSet<Point>, reached: &mut HashSet<Point>, curr: Point) {
if reached.contains(&curr) {
return;
}
reached.insert(curr);
for neighbor in curr.neighbors() {
if air.contains(&neighbor) {
flood_fill(air, reached, neighbor);
}
}
}
fn main() {
let mut args = ::std::env::args().skip(1);
let file = args.next().unwrap_or_else(|| String::from("./input"));
let solve = args
.next()
.map(|arg| match arg.as_str() {
"first" => SolvePuzzle::First,
"second" => SolvePuzzle::Second,
_ => unreachable!(),
})
.unwrap_or_default();
let file = BufReader::new(File::open(file).expect("Opening file"));
match solve {
SolvePuzzle::First => println!("{}", solve_first_puzzle(file)),
SolvePuzzle::Second => println!("{}", solve_second_puzzle(file)),
};
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn example_on_first() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_first_puzzle(reader), 64);
}
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_second_puzzle(reader), 58);
}
#[test]
fn basic_examples() {
let reader = Cursor::new("1,1,1\n1,1,2");
assert_eq!(solve_first_puzzle(reader), 10);
}
}