day 14 finished

This commit is contained in:
Ruediger Ludwig 2023-02-11 13:15:10 +01:00
parent 2ad5f62785
commit a1f97bac83
4 changed files with 476 additions and 1 deletions

292
src/days/day14/mod.rs Normal file
View 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(())
}
}

View file

@ -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)),
}
}