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
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)
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
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
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
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
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)
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
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
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