zerosleeps

Since 2010

Advent of Code 2020 day 11

Advent of Code 2020 day 11. Again this is a slightly sanitised version of the code used to submit my answers, but there’s a still a lot of duplication.

The trickiest and messiest part of this was dealing with negative list indexes for seats right at the edge of the grid. I briefly considered adding a gutter to the grid but that would only have caused a different assortment of edge cases.

Used pytest for this one as well. Nicer output than unittest, but for dead-simple cases like this I don’t see much difference.

import pytest
from pathlib import Path

def directions():
    return [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]

def get_raw_input():
    return (Path(__file__).parent/'day_11_input.txt').read_text()

def parse_raw_input(raw_input):
    return [[char for char in line.strip()] for line in raw_input.strip().splitlines()]

def get_adjacent(grid, seat, position):
    if (y := seat[0]+position[0]) < 0:
        return None
    if (x := seat[1]+position[1]) < 0:
        return None
    try:
        return grid[y][x]
    except IndexError:
        return None

def get_visible(grid, seat, direction):
    y = seat[0]+direction[0]
    x = seat[1]+direction[1]

    try:
        while y >= 0 and x >= 0 and grid[y][x] == '.':
            y += direction[0]
            x += direction[1]
        if y >= 0 and x >= 0:
            return grid[y][x]
        else:
            return None
    except IndexError:
        return None

def count_all_occupied_adjacent(grid, seat):
    return len(
        [
            result for result
            in (get_adjacent(grid, seat, position_to_check) for position_to_check in directions())
            if result == '#'
        ]
    )

def count_all_occupied_visible(grid, seat):
    return len(
        [
            result for result
            in (get_visible(grid, seat, direction_to_check) for direction_to_check in directions())
            if result == '#'
        ]
    )

def count_all_occupied(grid):
    return len([seat for row in grid for seat in row if seat == '#'])

def tick(grid, method, trigger):
    new_grid = [[seat for seat in row] for row in grid]
    last_count = 0
    iterations = 0

    while last_count != count_all_occupied(new_grid) or last_count == 0:
        last_count = count_all_occupied(grid)

        for y, row in enumerate(grid):
            for x, seat in enumerate(row):
                current_seat = grid[y][x]
                if method == 'visible':
                    count_of_occupied = count_all_occupied_visible(grid, [y, x])
                else:
                    count_of_occupied = count_all_occupied_adjacent(grid, [y, x])

                if current_seat == 'L' and count_of_occupied == 0:
                    new_grid[y][x] = '#'
                elif current_seat == '#' and count_of_occupied >= trigger:
                    new_grid[y][x] = 'L'

        grid = [[seat for seat in row] for row in new_grid]

    return last_count

class TestExamples():
    @pytest.fixture
    def example_input(self):
        return """L.LL.LL.LL
            LLLLLLL.LL
            L.L.L..L..
            LLLL.LL.LL
            L.LL.LL.LL
            L.LLLLL.LL
            ..L.L.....
            LLLLLLLLLL
            L.LLLLLL.L
            L.LLLLL.LL"""

    def test_part_one_example(self, example_input):
        assert tick(parse_raw_input(example_input), 'adjacent', 4) == 37

    def test_part_two_example(self, example_input):
        assert tick(parse_raw_input(example_input), 'visible', 5) == 26

if __name__ == '__main__':
    print(tick(parse_raw_input(get_raw_input()), 'adjacent', 4))
    print(tick(parse_raw_input(get_raw_input()), 'visible', 5))