Ejemplo n.º 1
0
def __animate_lights(lights, handle_stuck_lights=False):
    """ Evaluates the light grid and returns its state at the next step in time. """

    width = len(lights[0])
    height = len(lights)

    corner_lights = [(0, 0), (0, height - 1), (width - 1, 0),
                     (width - 1, height - 1)]

    # Create a new array which hold the state of the lights at the next step in time
    new_lights = __create_blank_light_grid(width, height)

    # Iterate over each light and evaluate it to determine what its next state will be
    for x, y in nested_iterable(range(width), range(height)):
        if handle_stuck_lights and (x, y) in corner_lights:
            new_lights[y][x] = LIGHT_ON
            continue

        # Count the number of nearby lights which are on.
        nearby_on_lights = __count_nearby_on_lights(x, y, lights)

        if lights[y][x] == LIGHT_OFF:
            new_lights[y][x] = LIGHT_ON if nearby_on_lights == 3 else LIGHT_OFF
            continue

        new_lights[y][x] = LIGHT_ON if nearby_on_lights in (2,
                                                            3) else LIGHT_OFF

    return new_lights
Ejemplo n.º 2
0
def get_neighbors_of(pos_x,
                     pos_y,
                     grid,
                     include_diagonals=True,
                     with_coords=False):
    """ Returns a generator which yields all of neighbors of a particular position in a grid.
    Neighbors include all directly adjacent cells, as well as diagonals unless `include_diagonals`
    is False. If `with_coords` is True, this yields `(neighbor, (x,y) coords of neighbor)`. """

    max_x = len(grid[0])
    max_y = len(grid)

    neighboring_x = [
        x for x in [pos_x - 1, pos_x, pos_x + 1] if x >= 0 and x < max_x
    ]
    neighboring_y = [
        y for y in [pos_y - 1, pos_y, pos_y + 1] if y >= 0 and y < max_y
    ]

    for x, y in nested_iterable(neighboring_x, neighboring_y):
        # This isn't a neighbor, this is the light itself. Skip it.
        if (x, y) == (pos_x, pos_y):
            continue

        # This is a diagonal, only include it if the caller requested diagonals.
        if (x != pos_x) and (y != pos_y) and not include_diagonals:
            continue

        if not with_coords:
            yield grid[y][x]
        else:
            yield grid[y][x], (x, y)
Ejemplo n.º 3
0
def __evaluate_seating_area(seating_area):
    """ Evaluates the seating area and determines its state at the next step in time. """

    width = len(seating_area[0])
    height = len(seating_area)

    # Create a new array which hold the state of the seating area at the next step in time
    new_seating_area = __create_blank_seating_area(width, height)

    # Iterate over each seat and evaluate it to determine what its next state will be
    for x, y in nested_iterable(range(width), range(height)):
        seat = seating_area[y][x]

        # Floors will be floors.
        if seat == FLOOR:
            new_seating_area[y][x] = FLOOR
            continue

        # Count the number of nearby occupied seats.
        nearby_occupied_seats = __count_nearby_occupied_seats(x, y, seating_area)

        # If a seat is currently open, it'll become occupied if it has no nearby occupied seats,
        # otherwise it'll remain open.
        if seat == OPEN_SEAT:
            new_seating_area[y][x] = OCCUPIED_SEAT if nearby_occupied_seats == 0 else OPEN_SEAT
            continue

        # If a seat is currently occupied, it'll become open if there are __MAX_OCCUPIED_NEIGHBORS
        # or more in nearby occupied seats, otherwise it will remain occupied.
        if nearby_occupied_seats >= __MAX_OCCUPIED_NEIGHBORS:
            new_seating_area[y][x] = OPEN_SEAT
        else:
            new_seating_area[y][x] = OCCUPIED_SEAT

    return new_seating_area
Ejemplo n.º 4
0
def get_item_combos():
    """ Yields all valid combinations of items, each set as a list. """

    for weapon, armor in nested_iterable(weapons, armors):
        for num_rings in range(3):
            for ring_combo in combinations(rings, num_rings):
                items = [weapon, armor]
                items.extend(ring_combo)
                yield items
Ejemplo n.º 5
0
    def execute(self, lights):
        """ Executes the command. Lights is a dictionary where the key is the (x,y) coordinate of
        the individual light, and the value is the state of the light. Iterates over the grid of
        lights across the range specified by the start and end coordinates, executing the commands
        function against each light."""

        x_range = range(self.start_x, self.end_x + 1)
        y_range = range(self.start_y, self.end_y + 1)

        for coord in nested_iterable(x_range, y_range):
            lights[coord] = self.command_function(lights[coord])

        return lights
Ejemplo n.º 6
0
def _get_low_points(heightmap):
    """ Returns a dictionary (coordinate to value) all low points in the heightmap. A low point is
    any point which is lower than any of its immediate neighbors. """

    lowpoints = dict()

    for x, y in nested_iterable(range(len(heightmap[0])),
                                range(len(heightmap))):
        value = heightmap[y][x]

        # If the value is lower than all of its neighbors, it's a low point on the map.
        neighbors = list(
            get_neighbors_of(x, y, heightmap, include_diagonals=False))
        if all(value < n for n in neighbors):
            lowpoints[(x, y)] = value

    return lowpoints
Ejemplo n.º 7
0
def part_two(heightmap):
    basins = {}

    # Get the coordinates of all the low points
    lowpoints = _get_low_points(heightmap).keys()

    # Track a basins dictionary where the key is the lowpoint of that basin, and the value is a
    # set of all points in that basin.
    basins_map = {lowpoint: set() for lowpoint in lowpoints}

    # Separately keep a map of points to which basins they occupy. This will reduce the basin search
    # space later, since a given point's smaller neighbor's basin might already been known, and
    # they'll belong to the same basin.
    point_to_basin_map = dict()

    for x, y in nested_iterable(range(len(heightmap[0])),
                                range(len(heightmap))):
        # 9 is the hightest, those are basin borders and don't belong in any basin
        if heightmap[y][x] == 9:
            continue

        # A lowpoint defines its own basin, add it and contnue
        if (x, y) in lowpoints:
            basins_map[(x, y)].add((x, y))
            continue

        # Find the coordinate of the lowpoint which define the basin this point is in, and track
        # it as belong ing to that basin.
        basin = _find_basin(x, y, heightmap, lowpoints, point_to_basin_map)
        basins_map[basin].add((x, y))
        point_to_basin_map[(x, y)] = basin

    # Calculate all the basin sizes, and select the biggest three
    basin_sizes = [len(points) for points in basins_map.values()]
    biggest_basins = sorted(basin_sizes)[-3:]

    # Return the product of the biggest three basins
    return reduce(lambda a, b: a * b, biggest_basins)
Ejemplo n.º 8
0
 def __new_lights_grid():
     """ Returns a 1000x1000 grid of lights that all start off. """
     lights = dict()
     for coord in nested_iterable(range(1000), range(1000)):
         lights[coord] = 0
     return lights
Ejemplo n.º 9
0
 def _rect(x_size, y_size):
     """ Creates a rectangle of ON pixels of size x*y in the top-left corner of the screen. """
     for y, x in nested_iterable(range(y_size), range(x_size)):
         screen[y][x] = PIXEL_ON
Ejemplo n.º 10
0
def part_one(numbers, boards):
    for n, b in nested_iterable(numbers, boards):
        _apply_number(n, b)
        if _check_if_winner(b):
            return _sum_unmarked(b) * n