Day 7: Laboratories

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • cabhan@discuss.tchncs.de
    link
    fedilink
    arrow-up
    2
    ·
    3 days ago

    Rust

    Part 1 was quite straightforward: just keep track of a frontier of every beam, and keep following it, adding splitter locations to a set. Easy.

    I had to give some thought to Part 2 and ended up doing dynamic programming: for every position, keep track of how many timelines go through it, solving recursively where necesary.

    use std::collections::{HashMap, HashSet, VecDeque};
    
    use crate::{grid::{Coordinate, Direction, Grid}, solver::DaySolver};
    
    pub struct Day07Solver;
    
    impl DaySolver for Day07Solver {
        fn part1(&self, input: String) -> String {
            let grid = Grid::from_grid_string(
                &input,
                |c| match c {
                    'S' => Location::Start,
                    '^' => Location::Splitter,
                    '.' => Location::Empty,
                    _ => panic!("Invalid location type"),
                }
            );
    
            let mut reached_splitters: HashSet<Coordinate> = HashSet::new();
    
            let mut beam_coordinates: VecDeque<Coordinate> = grid.iter()
                .filter_map(|(c, l)| match l {
                    Location::Start => Some(c),
                    _ => None,
                })
                .collect();
    
            while let Some(next) = beam_coordinates.pop_front() {
                if let Some(next_coord) = grid.neighbor_in_direction(next, Direction::Down) {
                    match grid[next_coord] {
                        Location::Start => { panic!("Encountered a second start!"); },
                        Location::Splitter => {
                            if !reached_splitters.contains(&next_coord) {
                                reached_splitters.insert(next_coord);
                                [
                                    grid.neighbor_in_direction(next_coord, Direction::Left),
                                    grid.neighbor_in_direction(next_coord, Direction::Right),
                                ]
                                    .into_iter()
                                    .flatten()
                                    .for_each(|c| beam_coordinates.push_back(c));
                            }
                        },
                        Location::Empty => { beam_coordinates.push_back(next_coord); }
                    }
                }
            }
    
            reached_splitters
                .len()
                .to_string()
        }
    
        fn part2(&self, input: String) -> String {
            let grid = Grid::from_grid_string(
                &input,
                |c| match c {
                    'S' => Location::Start,
                    '^' => Location::Splitter,
                    '.' => Location::Empty,
                    _ => panic!("Invalid location type"),
                }
            );
    
            let start_coord = grid.iter().find(|(_, l)| **l == Location::Start).unwrap().0;
    
            let mut num_timelines: HashMap<Coordinate, usize> = HashMap::new();
            count_timelines(&mut num_timelines, &grid, 1, start_coord);
    
            num_timelines[&start_coord].to_string()
        }
    }
    
    fn count_timelines(
        num_timelines: &mut HashMap<Coordinate, usize>,
        grid: &Grid<Location>,
        timelines_so_far: usize,
        coordinate: Coordinate,
    ) {
        if num_timelines.contains_key(&coordinate) {
            return
        }
    
        match grid[coordinate] {
            Location::Splitter => {
                let neighbors: Vec<Coordinate> = [
                    grid.neighbor_in_direction(coordinate, Direction::Left),
                    grid.neighbor_in_direction(coordinate, Direction::Right),
                ]
                    .into_iter()
                    .flatten()
                    .collect();
    
                neighbors.iter()
                    .for_each(|next| {
                        count_timelines(num_timelines, grid, timelines_so_far, *next);
                    });
    
                let timelines_from_here = neighbors.iter().map(|c| num_timelines[c]).sum();
                num_timelines.insert(coordinate, timelines_from_here);
            },
            _ => {
                let timelines_from_here = if let Some(next) = grid.neighbor_in_direction(coordinate, Direction::Down) {
                    count_timelines(num_timelines, grid, timelines_so_far, next);
                    num_timelines[&next]
                } else {
                    timelines_so_far
                };
    
                num_timelines.insert(coordinate, timelines_from_here);
            },
        };
    }
    
    #[derive(PartialEq, Eq, Copy, Clone)]
    enum Location {
        Start,
        Splitter,
        Empty,
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn part1() {
            let input = include_str!("../../inputs/test/07");
    
            let solver = Day07Solver {};
            assert_eq!("21", solver.part1(input.to_string()));
        }
    
    
        #[test]
        fn part2() {
            let input = include_str!("../../inputs/test/07");
    
            let solver = Day07Solver {};
            assert_eq!("40", solver.part2(input.to_string()));
        }
    }