Solve day24
This commit is contained in:
parent
cab8962911
commit
4443c683a8
|
@ -8,3 +8,4 @@ build = true
|
|||
app = true
|
||||
|
||||
[dependencies]
|
||||
bitvec = "1.0.1"
|
||||
|
|
|
@ -1,3 +1,339 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
#![feature(iter_collect_into)]
|
||||
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
marker::PhantomData,
|
||||
ops::{BitAnd, BitOr, Index, IndexMut},
|
||||
};
|
||||
|
||||
use bitvec::{
|
||||
prelude::{BitArray, Lsb0},
|
||||
vec::BitVec,
|
||||
};
|
||||
|
||||
type Pos3D = (usize, usize, isize);
|
||||
type Pos2D = (usize, usize);
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
enum SolvePuzzle {
|
||||
First,
|
||||
#[default]
|
||||
Second,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
struct Spot(BitArray<u8, Lsb0>);
|
||||
|
||||
impl Spot {
|
||||
const EMPTY: Self = Self::from_raw(0b0000_0000);
|
||||
const WALL: Self = Self::from_raw(0b0000_0001);
|
||||
const NORTH: Self = Self::from_raw(0b0000_0010);
|
||||
const EAST: Self = Self::from_raw(0b0000_0100);
|
||||
const SOUTH: Self = Self::from_raw(0b0000_1000);
|
||||
const WEST: Self = Self::from_raw(0b0001_0000);
|
||||
|
||||
const fn from_byte(byte: u8) -> Self {
|
||||
match byte {
|
||||
b'#' => Self::WALL,
|
||||
b'^' => Self::NORTH,
|
||||
b'>' => Self::EAST,
|
||||
b'v' => Self::SOUTH,
|
||||
b'<' => Self::WEST,
|
||||
b'.' => Self::EMPTY,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn from_raw(data: u8) -> Self {
|
||||
Spot(BitArray {
|
||||
_ord: PhantomData,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RawPlayground {
|
||||
field: Vec<Spot>,
|
||||
width: usize,
|
||||
}
|
||||
|
||||
struct Playground {
|
||||
field: BitVec,
|
||||
width: usize,
|
||||
height: usize,
|
||||
history_len: usize,
|
||||
}
|
||||
|
||||
impl RawPlayground {
|
||||
pub fn parse_input<R: BufRead>(reader: R) -> Self {
|
||||
let (field, max_x) = reader
|
||||
.lines()
|
||||
.map(Result::unwrap)
|
||||
.enumerate()
|
||||
.flat_map(|(y, line)| {
|
||||
line.into_bytes()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(x, byte)| (x, y, byte))
|
||||
})
|
||||
.fold((vec![], 0), |(mut field, max_x), (x, _y, byte)| {
|
||||
field.push(Spot::from_byte(byte));
|
||||
(field, max_x.max(x))
|
||||
});
|
||||
RawPlayground {
|
||||
field,
|
||||
width: max_x + 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_to_3d(mut self) -> Playground {
|
||||
let height = self.field.len() / self.width;
|
||||
let mut field_3d = BitVec::<usize, Lsb0>::with_capacity((self.width * height).pow(2));
|
||||
let history_len = self.width * height;
|
||||
for _epoch in 0..history_len {
|
||||
self.field
|
||||
.iter()
|
||||
.map(|&spot| spot != Spot::EMPTY)
|
||||
.collect_into(&mut field_3d);
|
||||
self.advance_history();
|
||||
}
|
||||
Playground {
|
||||
history_len,
|
||||
field: field_3d,
|
||||
width: self.width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_history(&mut self) {
|
||||
let mut next = self.clone();
|
||||
let height = self.field.len() / self.width;
|
||||
let sanitize = |x, y| -> Pos2D {
|
||||
(
|
||||
if x == 0 {
|
||||
self.width - 2
|
||||
} else if x == self.width - 1 {
|
||||
1
|
||||
} else {
|
||||
x
|
||||
},
|
||||
if y == 0 {
|
||||
height - 2
|
||||
} else if y == height - 1 {
|
||||
1
|
||||
} else {
|
||||
y
|
||||
},
|
||||
)
|
||||
};
|
||||
for y in 1..height - 1 {
|
||||
for x in 1..self.width - 1 {
|
||||
let north = self[sanitize(x, y - 1)];
|
||||
let east = self[sanitize(x + 1, y)];
|
||||
let south = self[sanitize(x, y + 1)];
|
||||
let west = self[sanitize(x - 1, y)];
|
||||
next[(x, y)] = (north & Spot::SOUTH)
|
||||
| (east & Spot::WEST)
|
||||
| (south & Spot::NORTH)
|
||||
| (west & Spot::EAST);
|
||||
}
|
||||
}
|
||||
*self = next;
|
||||
}
|
||||
}
|
||||
|
||||
impl Playground {
|
||||
pub fn find_shortest_path_len_to_end(
|
||||
&self,
|
||||
start: Pos2D,
|
||||
end: Pos2D,
|
||||
initial_time: isize,
|
||||
) -> usize {
|
||||
let mut queued: HashSet<Pos3D> = HashSet::new();
|
||||
let mut open: VecDeque<Pos3D> = VecDeque::new();
|
||||
let start = (start.0, start.1, initial_time);
|
||||
open.push_back(start);
|
||||
queued.insert(start);
|
||||
while let Some((x, y, z)) = open.pop_front() {
|
||||
if x == end.0 && y == end.1 {
|
||||
return z as usize;
|
||||
}
|
||||
for neighbor in self.neighbors_of((x, y, z)) {
|
||||
if !queued.contains(&neighbor) {
|
||||
queued.insert(neighbor);
|
||||
open.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Did not reach the target");
|
||||
}
|
||||
|
||||
fn neighbors_of(&self, (x, y, z): Pos3D) -> impl Iterator<Item = Pos3D> + '_ {
|
||||
[
|
||||
(x.checked_sub(1), Some(y)),
|
||||
(Some(x), y.checked_sub(1)),
|
||||
(
|
||||
if x + 1 < self.width {
|
||||
Some(x + 1)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Some(y),
|
||||
),
|
||||
(
|
||||
Some(x),
|
||||
if y + 1 < self.height {
|
||||
Some(y + 1)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
(Some(x), Some(y)),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(x, y)| {
|
||||
if let (Some(x), Some(y)) = (x, y) {
|
||||
Some((x, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
// Always in the next layer
|
||||
.map(move |(x, y)| (x, y, z + 1))
|
||||
// Skip all blocked spots
|
||||
.filter(|&idx| !self[idx])
|
||||
}
|
||||
|
||||
const fn index_of(&self, (x, y, z): Pos3D) -> usize {
|
||||
let z = z.rem_euclid(self.history_len as isize) as usize;
|
||||
x + y * self.width + z * self.width * self.height
|
||||
}
|
||||
}
|
||||
|
||||
fn solve_first_puzzle<R: BufRead>(reader: R) -> usize {
|
||||
let playground = RawPlayground::parse_input(reader).convert_to_3d();
|
||||
let start = (0..)
|
||||
.map(|x| (x, 0))
|
||||
.find(|&(x, y)| !playground[(x, y, 0)])
|
||||
.unwrap();
|
||||
let end = (0..)
|
||||
.map(|x| (x, playground.height - 1))
|
||||
.find(|&(x, y)| !playground[(x, y, 0)])
|
||||
.unwrap();
|
||||
playground.find_shortest_path_len_to_end(start, end, 0)
|
||||
}
|
||||
|
||||
fn solve_second_puzzle<R: BufRead>(reader: R) -> usize {
|
||||
let playground = RawPlayground::parse_input(reader).convert_to_3d();
|
||||
let start = (0..)
|
||||
.map(|x| (x, 0))
|
||||
.find(|&(x, y)| !playground[(x, y, 0)])
|
||||
.unwrap();
|
||||
let end = (0..)
|
||||
.map(|x| (x, playground.height - 1))
|
||||
.find(|&(x, y)| !playground[(x, y, 0)])
|
||||
.unwrap();
|
||||
let first = playground.find_shortest_path_len_to_end(start, end, 0);
|
||||
let back = playground.find_shortest_path_len_to_end(end, start, first as isize);
|
||||
playground.find_shortest_path_len_to_end(start, end, back as isize)
|
||||
}
|
||||
|
||||
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)),
|
||||
};
|
||||
}
|
||||
|
||||
impl Index<Pos2D> for RawPlayground {
|
||||
type Output = Spot;
|
||||
|
||||
fn index(&self, (x, y): Pos2D) -> &Self::Output {
|
||||
&self.field[x + y * self.width]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<Pos2D> for RawPlayground {
|
||||
fn index_mut(&mut self, (x, y): Pos2D) -> &mut Self::Output {
|
||||
&mut self.field[x + y * self.width]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Spot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let char = if *self == Self::EMPTY {
|
||||
String::from(".")
|
||||
} else if *self == Self::WALL {
|
||||
String::from("#")
|
||||
} else if *self == Self::NORTH {
|
||||
String::from("^")
|
||||
} else if *self == Self::EAST {
|
||||
String::from(">")
|
||||
} else if *self == Self::SOUTH {
|
||||
String::from("v")
|
||||
} else if *self == Self::WEST {
|
||||
String::from("<")
|
||||
} else {
|
||||
format!("{}", self.0.count_ones())
|
||||
};
|
||||
write!(f, "{}", char)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Spot {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Spot {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Pos3D> for Playground {
|
||||
type Output = bool;
|
||||
|
||||
fn index(&self, coord: Pos3D) -> &Self::Output {
|
||||
&self.field[self.index_of(coord)]
|
||||
}
|
||||
}
|
||||
|
||||
#[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), 18);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_on_second() {
|
||||
let reader = Cursor::new(include_str!("../input.test"));
|
||||
assert_eq!(solve_second_puzzle(reader), 54);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue