def __str__(self): output = list() for y_val in range(self._target_coord.y_val + 1): for x_val in range(self._target_coord.x_val + 1): coord = Coord(x_val, y_val) if coord == Coord(0, 0): output += 'M' elif coord == self._target_coord: output += 'T' else: erosion_type = self.get_map_data(coord).erosion_type if erosion_type == self.Type.ROCKY: output += '.' elif erosion_type == self.Type.WET: output += '=' elif erosion_type == self.Type.NARROW: output += '|' else: assert False output += '\n' # Strip final newline. return ''.join(output[:-1])
def find_largest_power( serial_num, grid_size_min, grid_size_max ): grid = compute_grid( serial_num ) largest_power = calc_power_box( grid, Coord(0, 0), grid_size_min ) largest_coord = Coord( 0, 0 ) largest_grid_size = 0 for grid_size in range( grid_size_min, grid_size_max + 1 ): for y_index in range(0, 300 - (grid_size - 1)): for x_index in range( 0, 300 - ( grid_size - 1) ): coord = Coord( x_index, y_index ) if x_index == 0 and y_index == 0: power = calc_power_box( grid, coord, grid_size ) last_x0_power = power elif x_index == 0: power = calc_power_delta_y( last_x0_power, grid, coord, grid_size ) last_x0_power = power else: power = calc_power_delta_x( power, grid, coord, grid_size ) if power > largest_power: largest_power = power largest_coord = coord largest_grid_size = grid_size return largest_coord, largest_grid_size
def test_get_nearby_coords(self): nearby_set = day15.get_nearby_set(Coord(2, 5)) nearby_list = list(nearby_set) day15.sort_coord_list_by_reading_order(nearby_list) self.assertEqual( [Coord(2, 4), Coord(1, 5), Coord(3, 5), Coord(2, 6)], nearby_list)
def find_furthest_room(self): # Perform a breadth first search. paths = deque([(Coord(0, 0), '')]) rooms_visited = set(Coord(0, 0)) num_doors = 0 num_doors_is_1000_or_more = 0 while paths: current_coord, current_path = paths.popleft() for direction, increment in self.direction_mapping.items(): next_coord = aoc.add_coords(current_coord, increment) if next_coord in rooms_visited: continue if self._is_door(current_coord, next_coord): next_path = current_path + direction paths.append((next_coord, next_path)) num_doors = max(num_doors, len(next_path)) # Most of these paths have common beginnings to we only need to re-simulate # if we haven't done next_path already when simulating a previous path. if next_coord not in rooms_visited: rooms_visited.add(next_coord) if num_doors >= 1000: num_doors_is_1000_or_more += 1 return num_doors, num_doors_is_1000_or_more
def get_map_data(self, coord): data = self._map_data.get(coord) # Return cached data. if data: return data # Otherwise calculate data to cache. This will use recursion to # calculate most data points. if coord == Coord(0,0): geologic_index = 0 elif coord == self._target_coord: geologic_index = 0 elif coord.y_val == 0: geologic_index = coord.x_val * 16807 elif coord.x_val == 0: geologic_index = coord.y_val * 48271 else: # Recursively calculate data. coord1 = aoc.add_coords(coord, Coord(-1,0)) coord2 = aoc.add_coords(coord, Coord(0,-1)) geologic_index = self.get_map_data(coord1).erosion_level * \ self.get_map_data(coord2).erosion_level erosion_level = (geologic_index + self._depth) % 20183 erosion_type = self.Type(erosion_level % 3) self._map_data[coord] = self.CaveData(erosion_level=erosion_level, erosion_type=erosion_type) return self._map_data[coord]
def _coord_is_in_reservoir(self, coord): # We can only check coordinates that aren't yet mapped that are mapped below. assert self._ground_map.get(coord) is None assert coord.y_val == self._max_coord.y_val or \ self._ground_map.get(Coord(coord.x_val, coord.y_val + 1)) is not None # Check by extending the region to the left and then to the right. for increment in (-1, 1): current_coord = coord while True: next_coord = Coord(current_coord.x_val + increment, current_coord.y_val) if not self._min_coord.x_val < next_coord.x_val < self._max_coord.x_val: return False coord_below = Coord(next_coord.x_val, next_coord.y_val + 1) contents = self._ground_map.get(coord_below) # There not clay below or resting water below (which has clay below it). if contents not in ('#', '~'): return False contents = self._ground_map.get(next_coord) # There is clay in this direction. if contents in ('#', '~'): break # Flowing water only happens outside of reservoirs. if contents == '|': return False current_coord = next_coord return True
def get_nearby_set(coord): return { Coord(coord.x_val, coord.y_val - 1), Coord(coord.x_val - 1, coord.y_val), Coord(coord.x_val + 1, coord.y_val), Coord(coord.x_val, coord.y_val + 1) }
def __init__(self, input_list): line_num = 0 self.lumber_map = dict() x_val = 0 # Make pylint happy. for line in input_list: for x_val, contents in enumerate(line): self.lumber_map[Coord(x_val, line_num)] = contents line_num += 1 self.map_size = Coord(x_val + 1, line_num)
def __init__(self, map_regex_with_detours): sys.setrecursionlimit(10000) # Needed to compile the complex regex. map_regex, self._longest_detour = self._remove_detours_from_regex( map_regex_with_detours) self._map_regex_with_detours = regex.compile(map_regex_with_detours) self._map_regex = regex.compile(map_regex) # Store the map as a sparse dictionary of doors which will be '-' or '|'. self._map_data = dict() self._min_coord = Coord(0, 0) self._max_coord = Coord(0, 0)
def simulate(input_list): """ Simulate both part 1 and part 2 at the same time. """ path1, path2 = parse_input(input_list[0], input_list[1]) # Walk the first wire path and create a map containing the coordinates the first wire touches. # We store the minimum length to the coordinate. wire_map = {} current_coord = Coord(0, 0) length = 0 for segment in path1: direction = segment[0] distance = int(segment[1:]) for _ in range(distance): length += 1 current_coord = aoc.add_coords(current_coord, NEXT_INCREMENT[direction]) if current_coord not in wire_map: wire_map[current_coord] = length # Walk the second wire path checking for intersections with the first wire. If we intersect, # calculate both the minimum distance to the port (0,0) and the minimum wire length to the # intersection. current_coord = Coord(0, 0) length = 0 min_distance_from_port = None min_length = None for segment in path2: direction = segment[0] distance = int(segment[1:]) for _ in range(distance): length += 1 current_coord = aoc.add_coords(current_coord, NEXT_INCREMENT[direction]) if current_coord in wire_map: distance_from_port = abs(current_coord.x_val) + abs( current_coord.y_val) if min_distance_from_port is None or distance_from_port < min_distance_from_port: min_distance_from_port = distance_from_port total_length = wire_map[current_coord] + length if min_length is None or total_length < min_length: min_length = total_length return min_distance_from_port, min_length
def move(self): if self.direction == '>': next_location = Coord(self.location.x_val + 1, self.location.y_val) elif self.direction == '<': next_location = Coord(self.location.x_val - 1, self.location.y_val) elif self.direction == '^': next_location = Coord(self.location.x_val, self.location.y_val - 1) else: # self.direction == 'v': next_location = Coord(self.location.x_val, self.location.y_val + 1) self.location = next_location self.turn_me(self.track[next_location]) return self.location
def calc_bound(data): coord = data[0].coord x_min = x_max = coord.x_val y_min = y_max = coord.y_val for star_data in data: x_min = min(x_min, star_data.coord.x_val) x_max = max(x_max, star_data.coord.x_val) y_min = min(y_min, star_data.coord.y_val) y_max = max(y_max, star_data.coord.y_val) return Coord(x_min, y_min), Coord(x_max, y_max)
def risk_level(self): level = 0 for y_val in range(self._target_coord.y_val + 1): for x_val in range(self._target_coord.x_val + 1): coord = Coord(x_val, y_val) level += self.get_map_data(coord).erosion_type return level
def simulate(self): new_lumber_map = dict() for y_index in range(self.map_size.y_val): for x_index in range(self.map_size.x_val): coord = Coord(x_index, y_index) (tree_neighbors, lumberyard_neighbors) = self.count_neighbors(coord) current_contents = self.lumber_map[coord] new_contents = current_contents if current_contents == '.': if tree_neighbors >= 3: new_contents = '|' elif current_contents == '|': if lumberyard_neighbors >= 3: new_contents = '#' elif current_contents == '#': if lumberyard_neighbors < 1 or tree_neighbors < 1: new_contents = '.' else: assert False new_lumber_map[coord] = new_contents self.lumber_map = new_lumber_map
def _is_door(self, coord1, coord2): if coord2 < coord1: coord1, coord2 = coord2, coord1 assert coord1 != coord2 assert coord1.x_val == coord2.x_val and coord1.y_val == coord2.y_val - 1 or \ coord1.x_val == coord2.x_val - 1 and coord1.y_val == coord2.y_val return Coord(coord1, coord2) in self._map_data
def compute_grid( serial_num ): grid = [ [0] * 300 for i in range( 300 ) ] for x_index in range( 300 ): for y_index in range( 300 ): grid[ x_index ][ y_index ] = calc_power( serial_num, Coord( x_index, y_index) ) return grid
def parse_input(input_list): # Map of velocitys keyed by coordinate. data = list() # Example input data # position=< 9, 1> velocity=< 0, 2> regex = r'position=<\s*(-?\d+),\s*(-?\d+)> velocity=<\s*(-?\d+),\s*(-?\d+)>' for line in input_list: match_obj = re.search(regex, line) assert match_obj coord = Coord(int(match_obj.group(1)), int(match_obj.group(2))) vel = Coord(int(match_obj.group(3)), int(match_obj.group(4))) data.append(StarData(coord, vel)) return data
def generate_map(self): final_paths = self._find_paths(Coord(0, 0), '') paths_simulated = set() # Re-walk the final_paths taking detours. for path in final_paths: new_path = '' coord = Coord(0, 0) for char in path: new_path += char coord = aoc.add_coords(coord, self.direction_mapping[char]) if new_path not in paths_simulated: self._find_paths(coord, new_path, max_depth=self._longest_detour, take_detours=True) paths_simulated.add(new_path)
def __str__(self): output = '' for y_index in range(self.map_size.y_val): for x_index in range(self.map_size.x_val): output += self.lumber_map[Coord(x_index, y_index)] output += '\n' # Strip final newline. return output[:-1]
def __init__(self, input_list): # Initialize the map with the spring. self._ground_map = dict({self.spring_coord : '+'}) self._amount_of_water_resting = 0 self._amount_of_water_flowing = 0 re_x = re.compile(r'^x=(\d+), y=(\d+)..(\d+)$') re_y = re.compile(r'^y=(\d+), x=(\d+)..(\d+)$') for line_num, line in enumerate(input_list): match = re_x.search(line) if match: xstart = int(match.group(1)) xend = int(match.group(1)) + 1 ystart = int(match.group(2)) yend = int(match.group(3)) + 1 match = re_y.search(line) if match: ystart = int(match.group(1)) yend = int(match.group(1)) + 1 xstart = int(match.group(2)) xend = int(match.group(3)) + 1 if line_num == 0: min_coord_x = xstart max_coord_x = xend - 1 min_coord_y = ystart max_coord_y = yend - 1 else: min_coord_x = min(min_coord_x, xstart) max_coord_x = max(max_coord_x, xend) min_coord_y = min(min_coord_y, ystart) max_coord_y = max(max_coord_y, yend) for x_coord in range(xstart, xend): for y_coord in range(ystart, yend): self._ground_map[Coord(x_coord, y_coord)] = '#' # Add a column of output padding for water flow. self._min_coord = Coord(min_coord_x - 1, min_coord_y) self._max_coord = Coord(max_coord_x, max_coord_y - 1)
def test_is_enemy_nearby(self): map_data = day15.parse_input(self.map_data_input_choose_open_tests) # Not close self.assertFalse(day15.is_enemy_nearby(map_data, 'G', Coord(1, 1))) # Is Diagonal self.assertFalse(day15.is_enemy_nearby(map_data, 'E', Coord(2, 2))) self.assertFalse(day15.is_enemy_nearby(map_data, 'G', Coord(3, 2))) self.assertFalse(day15.is_enemy_nearby(map_data, 'G', Coord(1, 2))) self.assertFalse(day15.is_enemy_nearby(map_data, 'G', Coord(3, 2))) # I am above self.assertTrue(day15.is_enemy_nearby(map_data, 'G', Coord(2, 2))) # I am below self.assertTrue(day15.is_enemy_nearby(map_data, 'E', Coord(1, 2))) # I am left self.assertTrue(day15.is_enemy_nearby(map_data, 'G', Coord(1, 3))) # I am right self.assertTrue(day15.is_enemy_nearby(map_data, 'G', Coord(3, 3)))
def parse_input(input_list): assert input_list[0][0:7] == 'depth: ' assert input_list[1][0:8] == 'target: ' index = input_list[0].find(' ') depth = int(input_list[0][index+1:]) index = input_list[1].find(' ') index2 = input_list[1].find(',') x_val = int(input_list[1][index+1:index2]) y_val = int(input_list[1][index2+1:]) return depth, Coord(x_val, y_val)
def get_stats(self): tree_acres = 0 lumberyard_acres = 0 for y_index in range(self.map_size.y_val): for x_index in range(self.map_size.x_val): contents = self.lumber_map[Coord(x_index, y_index)] if contents == '#': lumberyard_acres += 1 elif contents == '|': tree_acres += 1 return tree_acres, lumberyard_acres
def test_get_turn_order(self): map_data = day15.parse_input(self.map_data_input_order_tests) order = day15.get_turn_order(map_data) self.assertEqual([ Coord(2, 1), Coord(4, 1), Coord(1, 2), Coord(3, 2), Coord(5, 2), Coord(2, 3), Coord(4, 3) ], list(map(lambda square: square.coord, order)))
def __str__(self): output = list() # Add bottom. output += '#' * (( (self._max_coord.x_val - self._min_coord.x_val + 1) * 2) + 1) + '\n' for y_val in range(self._max_coord.y_val, self._min_coord.y_val - 1, -1): # Draw row containing '|' doors output += '#.' for x_val in range(self._min_coord.x_val, self._max_coord.x_val): coord1 = Coord(x_val, y_val) coord2 = Coord(x_val + 1, y_val) output += self._map_data.get((coord1, coord2), '#') if coord2 == Coord(0, 0): output += 'X' else: output += '.' output += '#\n' # Draw row containing '-' doors. This will also draw the bottom perimeter wall because # it goes one index past self._min_coord.y. output += '#' for x_val in range(self._min_coord.x_val, self._max_coord.x_val + 1): coord1 = Coord(x_val, y_val - 1) coord2 = Coord(x_val, y_val) output += self._map_data.get((coord1, coord2), '#') output += '#' output += '\n' # Strip final '\n' from output. return ''.join(output[:-1])
def count_neighbors(self, coord): tree_neighbors = 0 lumberyard_neighbors = 0 for y_index in range(coord.y_val - 1, coord.y_val + 2): for x_index in range(coord.x_val - 1, coord.x_val + 2): neighbor_coord = Coord(x_index, y_index) if neighbor_coord == coord: continue neighbor_contents = self.lumber_map.get(neighbor_coord) if neighbor_contents == '|': tree_neighbors += 1 elif neighbor_contents == '#': lumberyard_neighbors += 1 return tree_neighbors, lumberyard_neighbors
def __str__(self): output = '' row_label_length = len("%d" % self._max_coord.y_val) col_label_length = len("%d" % self._max_coord.x_val) for y_index in range(0, col_label_length): output += "%*s" % ( row_label_length + 1, ' ') for x_index in range(self._min_coord.x_val, self._max_coord.x_val + 1): col_label = "%*d" % (col_label_length, x_index) output += col_label[y_index] output += '\n' for y_index in range(0, self._max_coord.y_val + 2): output += "%*d " % (row_label_length, y_index) for x_index in range(self._min_coord.x_val, self._max_coord.x_val + 1): coord = Coord(x_index, y_index) output += self._ground_map.get(coord, '.') output += '\n' return output[0:-1] # Strip final newline.
def print_map(map_data, show_hitpoints=False): output = '' for y_val in range(map_data.size.y_val): hit_points = list() for x_val in range(map_data.size.x_val): square = map_data.grid.get(Coord(x_val, y_val)) if square: output += square.contains if square.contains in 'GE': hit_points.append("%s(%d)" % (square.contains, square.hit_points)) else: output += '.' if hit_points and show_hitpoints: output += ' ' + ', '.join(hit_points) output += '\n' return output
def parse_input(input_list): carts = dict() track = dict() line_num = 0 for line in input_list: for index, symbol in enumerate(line): if symbol in r'\/-|+': track[Coord(index, line_num)] = symbol elif symbol in 'v^': carts[Coord(index, line_num)] = Cart(Coord(index, line_num), symbol, track) track[Coord(index, line_num)] = '|' elif symbol in '<>': carts[Coord(index, line_num)] = Cart(Coord(index, line_num), symbol, track) track[Coord(index, line_num)] = '-' line_num += 1 return carts
def parse_input(input_list, elf_attack_power=3): # Map is a dictionary with Coord keys. map_data = MapData() x_val = 0 y_val = 0 for y_val, line in enumerate(input_list): for x_val, char in enumerate(line): if char == '.': pass elif char == 'G': map_data.grid[Coord(x_val, y_val)] = \ SquareData('G', 'E', Coord(x_val, y_val), 3, 200) elif char == 'E': map_data.grid[Coord(x_val, y_val)] = \ SquareData('E', 'G', Coord(x_val, y_val), elf_attack_power, 200) elif char == '#': map_data.grid[Coord(x_val, y_val)] = \ SquareData('#', '#', Coord(x_val, y_val), 0, 0) else: assert False # Bad input map_data.size = Coord(x_val + 1, y_val + 1) return map_data