day17 finished
This commit is contained in:
parent
6f3e94c5d1
commit
75c7b08449
5 changed files with 446 additions and 1 deletions
424
src/days/day17/mod.rs
Normal file
424
src/days/day17/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue