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

you are viewing a single comment's thread
view the rest of the comments
[โ€“] 2 points 10 months 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()));
    }
}
  • source