day 14 finished
This commit is contained in:
parent
2ad5f62785
commit
a1f97bac83
4 changed files with 476 additions and 1 deletions
292
src/days/day14/mod.rs
Normal file
292
src/days/day14/mod.rs
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
use itertools::Itertools;
|
||||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::common::pos::Pos;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
|
||||
const DAY_NUMBER: usize = 14;
|
||||
|
||||
pub struct Day;
|
||||
|
||||
impl DayTrait for Day {
|
||||
fn get_day_number(&self) -> usize {
|
||||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::parse(lines)?;
|
||||
Ok(ResultType::Integer(cave.drop_bottomless() as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::parse(lines)?;
|
||||
Ok(ResultType::Integer(cave.drop_floor()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum CaveError {
|
||||
#[error("Could not parse")]
|
||||
ParseError,
|
||||
|
||||
#[error("Error, while paring int")]
|
||||
NotAValidInteger(#[from] ParseIntError),
|
||||
|
||||
#[error("Not a valid Pos {0}")]
|
||||
NotAValidPos(String),
|
||||
|
||||
#[error("Found an empty path")]
|
||||
EmptyPath,
|
||||
|
||||
#[error("Found an empty cave")]
|
||||
EmptyCave,
|
||||
}
|
||||
|
||||
struct Cave {
|
||||
map: Vec<Vec<bool>>,
|
||||
min_x: i32,
|
||||
max_x: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl Cave {
|
||||
fn get(&self, map: &[Vec<bool>], pos: Pos<i32>) -> bool {
|
||||
if pos.y() >= self.height || pos.x() < self.min_x || pos.x() > self.max_x {
|
||||
false
|
||||
} else {
|
||||
map[pos.y() as usize][(pos.x() - self.min_x) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, map: &mut [Vec<bool>], pos: Pos<i32>) {
|
||||
map[pos.y() as usize][(pos.x() - self.min_x) as usize] = true
|
||||
}
|
||||
|
||||
fn walk(from: Pos<i32>, to: Pos<i32>) -> Result<Vec<Pos<i32>>, CaveError> {
|
||||
if from == to {
|
||||
return Ok(vec![from]);
|
||||
}
|
||||
if from.x() == to.x() {
|
||||
if from.y() < to.y() {
|
||||
Ok((from.y()..to.y()).map(|y| Pos::new(from.x(), y)).collect())
|
||||
} else {
|
||||
Ok(((to.y() + 1)..=from.y())
|
||||
.map(|y| Pos::new(from.x(), y))
|
||||
.collect())
|
||||
}
|
||||
} else if from.y() == to.y() {
|
||||
if from.x() < to.x() {
|
||||
Ok((from.x()..to.x()).map(|x| Pos::new(x, from.y())).collect())
|
||||
} else {
|
||||
Ok(((to.x() + 1)..=from.x())
|
||||
.map(|x| Pos::new(x, from.y()))
|
||||
.collect())
|
||||
}
|
||||
} else {
|
||||
Err(CaveError::ParseError)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_one(line: &str) -> Result<HashSet<Pos<i32>>, CaveError> {
|
||||
let corners = line
|
||||
.split("->")
|
||||
.map(|coord| match coord.split(',').collect::<Vec<_>>()[..] {
|
||||
[first, second] => {
|
||||
let x: i32 = first.trim().parse()?;
|
||||
let y = second.trim().parse()?;
|
||||
Ok(Pos::new(x, y))
|
||||
}
|
||||
_ => Err(CaveError::NotAValidPos(coord.to_owned())),
|
||||
})
|
||||
.collect::<Result<Vec<_>, CaveError>>()?;
|
||||
|
||||
if corners.is_empty() {
|
||||
return Err(CaveError::EmptyPath);
|
||||
}
|
||||
|
||||
let blocks = corners
|
||||
.iter()
|
||||
.tuple_windows()
|
||||
.map(|(first, second)| Cave::walk(*first, *second))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let mut blocks = blocks.iter().flatten().copied().collect::<HashSet<_>>();
|
||||
blocks.insert(*corners.last().unwrap());
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> Result<Cave, CaveError> {
|
||||
let mut cave: HashSet<Pos<i32>> = HashSet::new();
|
||||
for line in lines {
|
||||
cave.extend(Cave::parse_one(line)?.iter());
|
||||
}
|
||||
if cave.is_empty() {
|
||||
Err(CaveError::EmptyCave)
|
||||
} else {
|
||||
let mut max_x = i32::MIN;
|
||||
let mut min_x = i32::MAX;
|
||||
let mut max_y = i32::MIN;
|
||||
for block in &cave {
|
||||
if block.x() < min_x {
|
||||
min_x = block.x();
|
||||
}
|
||||
if block.x() > max_x {
|
||||
max_x = block.x();
|
||||
}
|
||||
if block.y() > max_y {
|
||||
max_y = block.y()
|
||||
}
|
||||
}
|
||||
let mut map = vec![vec![false; (max_x - min_x + 1) as usize]; (max_y + 1) as usize];
|
||||
for block in cave {
|
||||
map[block.y() as usize][(block.x() - min_x) as usize] = true;
|
||||
}
|
||||
Ok(Cave {
|
||||
map,
|
||||
min_x,
|
||||
max_x,
|
||||
height: max_y + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_bottomless(&self) -> u32 {
|
||||
let mut drops = 0;
|
||||
let mut filled_map = self.map.to_vec();
|
||||
|
||||
while let Some(next) = self.drop_one(&filled_map) {
|
||||
if next.y() <= self.height {
|
||||
drops += 1;
|
||||
self.set(&mut filled_map, next);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
drops
|
||||
}
|
||||
|
||||
fn drop_one(&self, map: &[Vec<bool>]) -> Option<Pos<i32>> {
|
||||
let mut drop = Pos::new(500, 0);
|
||||
loop {
|
||||
let next_y = drop.y() + 1;
|
||||
if next_y > self.height {
|
||||
return None;
|
||||
}
|
||||
let mut stuck = true;
|
||||
for dx in [0, -1, 1] {
|
||||
let next = Pos::new(drop.x() + dx, next_y);
|
||||
if !self.get(map, next) {
|
||||
drop = next;
|
||||
stuck = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if stuck {
|
||||
if drop.y() == 0 {
|
||||
return None;
|
||||
} else {
|
||||
return Some(drop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_floor(&self) -> i64 {
|
||||
let last_row = self.height + 1;
|
||||
let mut drops = 1;
|
||||
let mut row = vec![500];
|
||||
let mut left_exceed = None;
|
||||
let mut right_exceed = None;
|
||||
for y in 1..last_row {
|
||||
let mut next_row = Vec::new();
|
||||
for x in row {
|
||||
for dx in [-1, 0, 1] {
|
||||
let x = x + dx;
|
||||
if x < self.min_x {
|
||||
left_exceed = left_exceed.or(Some(y));
|
||||
} else if x > self.max_x {
|
||||
right_exceed = right_exceed.or(Some(y));
|
||||
} else if !next_row.contains(&x)
|
||||
&& (y >= self.height || !self.map[y as usize][(x - self.min_x) as usize])
|
||||
{
|
||||
next_row.push(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(left_exceed) = left_exceed {
|
||||
if !next_row.contains(&self.min_x) && (y >= self.height || !self.map[y as usize][0])
|
||||
{
|
||||
next_row.push(self.min_x);
|
||||
}
|
||||
drops += (y - left_exceed + 1) as i64;
|
||||
}
|
||||
if let Some(right_exceed) = right_exceed {
|
||||
if !next_row.contains(&self.max_x)
|
||||
&& (y >= self.height
|
||||
|| !self.map[y as usize][(self.max_x - self.min_x) as usize])
|
||||
{
|
||||
next_row.push(self.max_x);
|
||||
}
|
||||
drops += (y - right_exceed + 1) as i64;
|
||||
}
|
||||
drops += next_row.len() as i64;
|
||||
row = next_row
|
||||
}
|
||||
drops
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{common::file::read_lines, hashset};
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
let input = "498,4 -> 498,6 -> 496,6";
|
||||
let expected = hashset! {Pos::new(498, 4), Pos::new(498, 5), Pos::new(498, 6), Pos::new(497, 6), Pos::new(496, 6)};
|
||||
let result = Cave::parse_one(input)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let result = Cave::parse(&lines)?;
|
||||
assert_eq!(result.min_x, 494);
|
||||
assert_eq!(result.max_x, 503);
|
||||
assert_eq!(result.height, 10);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(24);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(93);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ mod day10;
|
|||
mod day11;
|
||||
mod day12;
|
||||
mod day13;
|
||||
mod day14;
|
||||
mod template;
|
||||
|
||||
pub use template::DayTrait;
|
||||
|
|
@ -20,7 +21,7 @@ pub mod day_provider {
|
|||
use super::*;
|
||||
use thiserror::Error;
|
||||
|
||||
const MAX_DAY: usize = 13;
|
||||
const MAX_DAY: usize = 14;
|
||||
|
||||
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
||||
match day_num {
|
||||
|
|
@ -37,6 +38,7 @@ pub mod day_provider {
|
|||
11 => Ok(Box::new(day11::Day)),
|
||||
12 => Ok(Box::new(day12::Day)),
|
||||
13 => Ok(Box::new(day13::Day)),
|
||||
14 => Ok(Box::new(day14::Day)),
|
||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue