diff --git a/samples/day17.txt b/samples/day17.txt new file mode 100644 index 0000000..97a1aa1 --- /dev/null +++ b/samples/day17.txt @@ -0,0 +1 @@ +>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>> diff --git a/src/bin/day17.rs b/src/bin/day17.rs new file mode 100644 index 0000000..0793bc0 --- /dev/null +++ b/src/bin/day17.rs @@ -0,0 +1,206 @@ +use std::collections::HashMap; + +use aoc_2022::*; + +type Input = Vec; +type Output = usize; + +type Position = Vector2D; + +struct InfiniteIndex { + size: usize, + i: usize, +} + +impl InfiniteIndex { + fn new(size: usize) -> InfiniteIndex { + InfiniteIndex { size, i: size - 1 } + } +} + +impl Iterator for InfiniteIndex { + type Item = usize; + + fn next(&mut self) -> Option { + self.i = (self.i + 1) % self.size; + Some(self.i) + } +} + +struct Tunnel { + buffer: usize, + jets: Vec, + rocks: Vec>, + flushed: usize, + next_jet: InfiniteIndex, + next_shape: InfiniteIndex, +} + +lazy_static! { + static ref SHAPES: Vec>> = vec![ + vec!["####"], + vec![" # ", "###", " # "], + vec![" #", " #", "###"], + vec!["#", "#", "#", "#"], + vec!["##", "##"], + ] + .iter() + .map(|shape| shape.iter().map(|l| l.chars().collect()).collect()) + .collect_vec(); +} + +fn below(position: Vector2D) -> Vector2D { + position + Vector2D::new(0, 1) +} + +fn left(position: Vector2D) -> Vector2D { + position + Vector2D::new(-1, 0) +} + +fn right(position: Vector2D) -> Vector2D { + position + Vector2D::new(1, 0) +} + +fn occupied(shape: &[Vec]) -> impl Iterator + '_ { + shape.iter().enumerate().flat_map(|(y, row)| { + row.iter().enumerate().filter_map(move |(x, c)| { + if c == &'#' { + Some(Vector2D::new(x as isize, y as isize)) + } else { + None + } + }) + }) +} + +impl Tunnel { + fn new(buffer: usize, jets: Vec) -> Tunnel { + let jet_count = jets.len(); + + Tunnel { + buffer, + jets, + rocks: vec!["#########".chars().collect_vec()], + flushed: 0, + next_jet: InfiniteIndex::new(jet_count), + next_shape: InfiniteIndex::new(SHAPES.len()), + } + } + + fn hit(&self, shape: &[Vec], position: Position) -> bool { + occupied(shape).any(|rock| self.rocks[position + rock] != ' ') + } + + fn emplace(&mut self, shape: &[Vec], position: Position) { + for rock in occupied(shape) { + self.rocks[position + rock] = '#'; + } + + while !self.rocks.first().unwrap().contains(&'#') { + self.rocks.remove(0); + } + + while self.rocks.len() > self.buffer { + self.rocks.pop(); + self.flushed += 1; + } + } + + fn add_one(&mut self) { + let shape = &SHAPES[self.next_shape.next().unwrap()]; + + for _ in 0..shape.len() + 3 { + self.rocks.insert(0, "| |".chars().collect_vec()); + } + + let mut position = Vector2D::new(3, 0); + loop { + let jet = self.jets[self.next_jet.next().unwrap()]; + + if jet == '>' && !self.hit(shape, right(position)) { + position = right(position); + } else if jet == '<' && !self.hit(shape, left(position)) { + position = left(position); + } + + if self.hit(shape, below(position)) { + break; + } + + position = below(position); + } + + self.emplace(shape, position); + } + + fn add(&mut self, mut count: usize) -> &Tunnel { + let mut seen: HashMap = HashMap::new(); + + while count > 0 { + let hash = self.rocks.iter().map(|l| l.iter().join("")).join(""); + + match seen.get(&hash) { + Some((to_add, height)) => { + let p_height = self.height() - height; + let p_length = to_add - count; + + self.flushed += (count / p_length) * p_height; + count %= p_length; + break; + } + None => { + seen.insert(hash, (count, self.height())); + self.add_one(); + count -= 1; + } + } + } + + for _ in 0..count { + self.add_one(); + } + + self + } + + fn height(&self) -> usize { + self.rocks.len() + self.flushed - 1 + } +} + +struct Day17; +impl Solution for Day17 { + fn parse_input>(pathname: P) -> Input { + file_to_string(pathname).trim_end().chars().collect() + } + + fn part_1(input: &Input) -> Output { + Tunnel::new(100, input.clone()).add(2022).height() + } + + fn part_2(input: &Input) -> Output { + Tunnel::new(100, input.clone()).add(1000000000000).height() + } +} + +fn main() -> Result<()> { + // Day17::run("sample") + Day17::main() +} + +test_sample!(day_17, Day17, 3068, 1514285714288); + +#[cfg(test)] +mod day_17_extended { + use super::*; + + #[test] + fn test_infinite_index() { + let index = InfiniteIndex::new(3); + + assert_eq!( + index.take(10).collect::>(), + vec![0, 1, 2, 0, 1, 2, 0, 1, 2, 0] + ); + } +}