Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2018-12-20 - A Regular Map

(original .ipynb)
#--------------------------------------------------------------------
# TODO: make sure to use my latest `advent` lib instead of this inline BS
class GrowableGrid():
    def __init__(self, start_node):
        self.rows = 1
        self.cols = 1
        self.start_row = 0
        self.start_col = 0
        self.row = 0
        self.col = 0
        self.grid = [[start_node]]
        self.stack = []

    def create_row(self):
        return [None] * self.cols

    def create_col(self):
        return [None] * self.cols

    def assert_not_set(self, row, col, node):
        existing_node = self.grid[row][col]
        if existing_node != node and existing_node is not None:
            raise Exception(
                f"Trying to insert {node} into (row={row}, col={col})" +
                f" but it already contains {self.grid[row][col]}")

    def up(self, node):
        if self.row == 0:
            new_row = self.create_row()
            self.grid.insert(0, new_row)
            self.rows += 1
            self.recalc_positions(1, 0)

        self.assert_not_set(self.row-1, self.col, node)
        self.grid[self.row-1][self.col] = node
        self.row -= 1

    def down(self, node):
        if self.row == self.rows - 1:
            new_row = self.create_row()
            self.grid.append(new_row)
            self.rows += 1

        self.assert_not_set(self.row+1, self.col, node)
        self.grid[self.row+1][self.col] = node
        self.row += 1

    def left(self, node):
        if self.col == 0:
            for row in self.grid:
                row.insert(0, None)
            self.recalc_positions(0, 1)
            self.cols += 1
            
        self.assert_not_set(self.row, self.col-1, node)
        self.grid[self.row][self.col-1] = node
        self.col -= 1

    def right(self, node):
        if self.col == self.cols - 1:
            for row in self.grid:
                row.append(None)
            self.cols += 1

        self.assert_not_set(self.row, self.col+1, node)
        self.grid[self.row][self.col+1] = node
        self.col += 1
        
    def recalc_positions(self, d_row, d_col):
        self.row += d_row
        self.col += d_col
        self.start_row += d_row
        self.start_col += d_col
        
        for i in range(len(self.stack)):
            row, col = self.stack[i]
            self.stack[i] = (row + d_row, col + d_col)

    def push_position(self):
        self.stack.append((self.row, self.col))
    
    def pop_position(self):
        row, col = self.stack.pop()
        self.row = row
        self.col = col

    def peek_position(self):
        row, col = self.stack[-1]
        self.row = row
        self.col = col

    def __str__(self):
        rs = []
        for row in self.grid:
            row_str = []
            for col in row:
                if col:
                    row_str.append(col)
                else:
                    row_str.append(" ")
            rs.append("".join(row_str))

        return "\n".join(rs)

def neighbours4(grid, r, c):
    """
        Returns the adjacent tiles NOT including diagonals
    """
    max_r = len(grid)
    max_c = len(grid[0])
    candidates = (
        (r-1, c), (r+1, c),
        (r, c-1), (r, c+1)
    )
    for maybe_r, maybe_c in candidates:
        if maybe_r == r and maybe_c == c:
            continue
        if maybe_r >= max_r or maybe_r < 0:
            continue
        if maybe_c >= max_c or maybe_c < 0:
            continue

        yield (maybe_r, maybe_c)

def expand_grid(i, regex_str, grid):
    grid.push_position()
    
    while i < len(regex_str):
        c = regex_str[i]
        if c == "(":
            r, c = (grid.row, grid.col)
            i = expand_grid(i+1, regex_str, grid)
            grid.row, grid.col = r, c
        elif c == ")":
            grid.pop_position()
            return i
        elif c == "|":
            grid.peek_position()
        else:
            if c == "N":
                grid.up("-")
                grid.up(".")
            elif c == "S":
                grid.down("-")
                grid.down(".")
            elif c == "E":
                grid.right("|")
                grid.right(".")
            elif c == "W":
                grid.left("|")
                grid.left(".")
        i += 1
    return i        
def solve(input_str):
    g = GrowableGrid("X")
    expand_grid(0, input_str, g)
    
    # make move grid
    move_grid = []
    for row in g.grid:
        move_grid.append([ None for _ in row ])
        
    move_grid[g.start_row][g.start_col] = 0
    queue = [(g.start_row, g.start_col)]
    doors = 1
    
    while any(queue):
        next_queue = []
        
        for r,c in queue:
            neighbours = neighbours4(g.grid, r, c)
            for nr, nc in neighbours:
                if g.grid[nr][nc] in ("-", "|", ".") and move_grid[nr][nc] == None:
                    dr = nr - r
                    dc = nc - c
                    next_queue.append((r + dr, c + dc))
                
                if move_grid[nr][nc] == None or move_grid[nr][nc] > doors:
                    if g.grid[nr][nc] == ".":
                        move_grid[nr][nc] = doors
                    else:
                        move_grid[nr][nc] = -1
        doors += 1
        queue = next_queue

    print("part 1:", (doors // 2) - 1)
    
    at_least_1k = 0
    for r, row in enumerate(g.grid):
        for c, col in enumerate(row):
            if col == ".":
                distance = move_grid[r][c]
                if distance//2 >= 1000:
                    at_least_1k += 1
    
    print("part 2:", at_least_1k)

puzzle_input_str = open("puzzle_input/day20.txt").read()
solve(puzzle_input_str)
part 1: 3755
part 2: 8627