day17 finished

This commit is contained in:
Rüdiger Ludwig 2023-07-28 23:19:14 +02:00
parent 6f3e94c5d1
commit 75c7b08449
5 changed files with 446 additions and 1 deletions

17
data/day17/blocks.txt Normal file
View file

@ -0,0 +1,17 @@
####
.#.
###
.#.
..#
..#
###
#
#
#
#
##
##

1
data/day17/example01.txt Normal file
View file

@ -0,0 +1 @@
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>

1
data/day17/input.txt Normal file

File diff suppressed because one or more lines are too long

424
src/days/day17/mod.rs Normal file
View file

@ -0,0 +1,424 @@
use std::{cell::Cell, marker::PhantomData, ops::Index};
use crate::common::file::read_lines;
use super::template::{DayTrait, ResultType};
use itertools::Itertools;
use thiserror::Error;
const DAY_NUMBER: usize = 17;
const STACK_WIDTH: usize = 7;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
let result = Day::run(pushes, 2022)?;
Ok(ResultType::Integer(result as i64))
}
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
let result = Day::run(pushes, 1000000000000)?;
Ok(ResultType::Integer(result as i64))
}
}
impl Day {
fn run(push_cycle: Dispenser<Push>, max_cycles: i64) -> Result<i64, RockError> {
let raw = read_lines(DAY_NUMBER, "blocks.txt")?;
let rock_cycle = Dispenser::new(Rock::parse(&raw)?);
let mut cycle = 0;
let mut stack = Stack::new();
let mut last_drop = None;
let mut prev_bottom = 0;
let mut additional_height = None;
while cycle < max_cycles {
let bottom = stack.one_rock(rock_cycle.next(), &push_cycle);
if bottom < prev_bottom {
let drop_distance = prev_bottom - bottom;
match last_drop {
None => {
last_drop = Some((bottom, drop_distance, cycle, None));
}
Some((_, last_distance, _, _)) if last_distance < drop_distance => {
last_drop = Some((bottom, drop_distance, cycle, None));
}
Some((last_bottom, last_distance, last_cycle, None))
if last_distance == drop_distance =>
{
last_drop = Some((
bottom,
drop_distance,
cycle,
Some((bottom - last_bottom, cycle - last_cycle)),
));
}
Some((last_bottom, last_distance, last_cycle, Some((growth, period))))
if last_distance == drop_distance =>
{
if growth == bottom - last_bottom && period == cycle - last_cycle {
let first_bottom = last_bottom - growth;
let all_equal = (0..growth).all(|row| {
stack.0[first_bottom + row] == stack.0[last_bottom + row]
});
if all_equal {
let iterations = (max_cycles - cycle) / period;
additional_height = Some(iterations * growth as i64);
cycle += iterations * period;
}
} else {
last_drop = Some((
bottom,
drop_distance,
cycle,
Some((bottom - last_bottom, cycle - last_cycle)),
))
}
}
_ => {}
}
}
prev_bottom = bottom;
cycle += 1;
}
match additional_height {
None => Ok(stack.height() as i64),
Some(height) => Ok(stack.height() as i64 + height),
}
}
}
#[derive(Debug, Error)]
enum RockError {
#[error("IO Error")]
RockIOError(#[from] std::io::Error),
#[error("A rock must not be zero length or hight")]
MustNotBeEmpty,
#[error("All lines in a block must be the same length")]
AllLinesSameLength,
#[error("Unknown direction: {0}")]
UnknownDirection(char),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Rock {
positions: Vec<(usize, usize)>,
width: usize,
height: usize,
}
impl Rock {
pub fn new(blocks: Vec<Vec<bool>>) -> Result<Rock, RockError> {
let height = blocks.len();
if height == 0 {
return Err(RockError::MustNotBeEmpty);
}
let width = blocks.iter().map(|line| line.len()).max().unwrap_or(0);
if width == 0 {
return Err(RockError::MustNotBeEmpty);
}
for line in blocks.iter() {
if line.len() != width {
return Err(RockError::AllLinesSameLength);
}
}
let positions = blocks
.iter()
.enumerate()
.map(|(y, line)| {
line.iter()
.enumerate()
.filter_map(move |(x, block)| block.then_some((x, height - 1 - y)))
})
.flatten()
.collect_vec();
Ok(Rock {
positions,
width,
height,
})
}
pub fn parse(lines: &[String]) -> Result<Vec<Rock>, RockError> {
lines
.iter()
.batching(|it| {
let blocks = it
.skip_while(|line| line.is_empty())
.take_while(|line| !line.is_empty())
.map(|line| line.chars().map(|c| c == '#').collect_vec())
.collect_vec();
if blocks.is_empty() {
None
} else {
Some(Rock::new(blocks))
}
})
.try_collect()
}
}
#[derive(Debug, Clone, Copy)]
enum Push {
Left,
Right,
}
impl Push {
pub fn parse(c: char) -> Result<Push, RockError> {
match c {
'<' => Ok(Push::Left),
'>' => Ok(Push::Right),
_ => Err(RockError::UnknownDirection(c)),
}
}
}
struct FallingRock<'a> {
rock: &'a Rock,
bottom: usize,
left: usize,
}
impl<'a> FallingRock<'a> {
pub fn new(rock: &'a Rock, stack_height: usize) -> FallingRock<'a> {
FallingRock {
rock,
bottom: stack_height + 3,
left: 2,
}
}
pub fn try_push(mut self, push: &Push, stack: &Stack) -> Self {
let left = match push {
Push::Left => {
if self.left == 0 {
return self;
}
self.left - 1
}
Push::Right => {
if self.left + self.rock.width >= STACK_WIDTH {
return self;
}
self.left + 1
}
};
if self.check_position(left, self.bottom, stack) {
self.left = left;
}
self
}
pub fn try_drop(mut self, stack: &Stack) -> Result<Self, Self> {
if self.bottom == 0 {
return Err(self);
}
let bottom = self.bottom - 1;
if self.check_position(self.left, bottom, stack) {
self.bottom = bottom;
Ok(self)
} else {
Err(self)
}
}
fn check_position(&self, left: usize, bottom: usize, stack: &Stack) -> bool {
self.rock
.positions
.iter()
.all(|(x, y)| *y + bottom >= stack.height() || !stack[*y + bottom][*x + left])
}
fn reach(&self) -> usize {
self.bottom + self.rock.height
}
fn positions(&'a self) -> impl Iterator<Item = (usize, usize)> + 'a {
self.rock
.positions
.iter()
.map(|(x, y)| (*x + self.left, *y + self.bottom))
}
}
struct Stack(Vec<[bool; STACK_WIDTH]>);
impl Stack {
pub fn new() -> Stack {
Stack(vec![])
}
#[inline]
pub fn height(&self) -> usize {
self.0.len()
}
pub fn settle_rock(&mut self, rock: FallingRock<'_>) {
for _ in self.0.len()..rock.reach() {
self.0.push([false; STACK_WIDTH]);
}
for (x, y) in rock.positions() {
self.0[y][x] = true
}
}
fn one_rock(&mut self, rock: &Rock, push_cycle: &Dispenser<Push>) -> usize {
let mut rock = FallingRock::new(rock, self.height());
loop {
let push = push_cycle.next();
rock = rock.try_push(push, &self);
match rock.try_drop(&self) {
Ok(next_rock) => rock = next_rock,
Err(rock) => {
let bottom = rock.bottom;
self.settle_rock(rock);
return bottom;
}
}
}
}
}
impl std::fmt::Display for Stack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = self
.0
.iter()
.rev()
.map(|line| line.iter().map(|b| if *b { "#" } else { " " }).join(""))
.join("\n");
write!(f, "{}", text)
}
}
impl Index<usize> for Stack {
type Output = [bool; STACK_WIDTH];
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
struct Dispenser<'a, T>
where
T: 'a,
{
data: Vec<T>,
current: Cell<usize>,
_marker: PhantomData<&'a T>,
}
impl<'a, T> Dispenser<'a, T> {
pub fn new(data: Vec<T>) -> Self {
Dispenser {
data,
current: Cell::new(0),
_marker: PhantomData,
}
}
fn next(&'a self) -> &'a T {
let idx = self.current.get();
self.current.set(self.current.get() + 1);
if self.current.get() >= self.data.len() {
self.current.set(0);
}
self.data.get(idx).unwrap()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_lines;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(3068);
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(1514285714288);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn read_blocks() -> Result<()> {
let day = Day {};
let raw = read_lines(day.get_day_number(), "blocks.txt")?;
let rocks = Rock::parse(&raw)?;
let expected = Rock::new(vec![vec![true]; 4])?;
assert_eq!(rocks[3], expected);
let angle_rock = &rocks[2];
assert_eq!(
angle_rock.positions,
vec![(2, 2), (2, 1), (0, 0), (1, 0), (2, 0)]
);
Ok(())
}
#[test]
fn drop_one() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
let raw = read_lines(day.get_day_number(), "blocks.txt")?;
let rocks = Dispenser::new(Rock::parse(&raw)?);
let mut stack = Stack::new();
stack.one_rock(rocks.next(), &pushes);
assert_eq!(stack.0, vec![[false, false, true, true, true, true, false]]);
Ok(())
}
#[test]
fn drop_some() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
let result = Day::run(pushes, 10)?;
assert_eq!(result, 17);
Ok(())
}
}

View file

@ -14,6 +14,7 @@ mod day13;
mod day14; mod day14;
mod day15; mod day15;
mod day16; mod day16;
mod day17;
mod template; mod template;
pub use template::DayTrait; pub use template::DayTrait;
@ -23,7 +24,7 @@ pub mod day_provider {
use super::*; use super::*;
use thiserror::Error; use thiserror::Error;
const MAX_DAY: usize = 16; const MAX_DAY: usize = 17;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> { pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num { match day_num {
@ -43,6 +44,7 @@ pub mod day_provider {
14 => Ok(Box::new(day14::Day)), 14 => Ok(Box::new(day14::Day)),
15 => Ok(Box::new(day15::Day)), 15 => Ok(Box::new(day15::Day)),
16 => Ok(Box::new(day16::Day)), 16 => Ok(Box::new(day16::Day)),
17 => Ok(Box::new(day17::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)), _ => Err(ProviderError::InvalidNumber(day_num)),
} }
} }