def __init__(self, file_name): lines = Input(file_name).lines() self.coordinates: Set[Coordinate] = set() self.grid = InfiniteGrid[Coordinate]() for line in lines: x, y = line.split(', ') coordinate = Coordinate(int(x), int(y), system=CoordinateSystem.X_RIGHT_Y_DOWN) self.coordinates.add(coordinate) self.grid[coordinate] = coordinate self.bounding_box: BoundingBox = self.grid.bounding_box for x, y in self.bounding_box: test_coordinate = Coordinate( x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) distances = dict( (coordinate, test_coordinate.manhattan(coordinate)) for coordinate in self.coordinates) _, min_distance = min(distances.items(), key=lambda i: i[1]) min_coordinates = [ coordinate for coordinate, distance in distances.items() if distance == min_distance ] if len(min_coordinates) != 1: continue self.grid[test_coordinate] = min_coordinates[0]
def __init__(self, file_name): self._grid: InfiniteGrid[int] = Input(file_name).grid().map( lambda x: int(x)) # self._graph = grid.to_graph(1, 2, 3, 4, 5, 6, 7, 8, 9) self.start = Coordinate(x=self._grid.min_x, y=self._grid.min_y, system=CoordinateSystem.X_RIGHT_Y_DOWN) self.size = self._grid.max_x + 1 self.end = Coordinate(x=self.size - 1, y=self.size - 1, system=CoordinateSystem.X_RIGHT_Y_DOWN)
def part1(self): grid = InfiniteGrid[bool]() santa = Coordinate(0, 0) grid[santa] = True for character in self.path: santa = santa.move(character) grid[santa] = True result = len(grid.find(True)) print("Part 1:", result)
def __init__(self, file_name): line = Input(file_name).line() target_regex = re.compile(r'target area: x=(-?\d+)..(-?\d+), y=(-?\d+)..(-?\d+)') match = target_regex.match(line) box = BoundingBox() box = box.expand_x(int(match.group(1))) box = box.expand_x(int(match.group(2))) box = box.expand_y(int(match.group(3))) box = box.expand_y(int(match.group(4))) self.target = box grid: InfiniteGrid[str] = InfiniteGrid[str]() start_x = 1 attempt = FiringAttempt(start_x, 0) while attempt.step_until(self.target) is None: start_x += 1 attempt = FiringAttempt(start_x, 0) starting_coordinate = Coordinate(start_x, 0) queue: Queue = Queue() queue.put(starting_coordinate) seen: Set[Coordinate] = set() seen.add(starting_coordinate) max_y = 0 while not queue.empty(): item: Coordinate = queue.get() attempt = FiringAttempt(item.x, item.y) end = attempt.step_until(self.target) if end is not None: max_y = max(max_y, attempt.max_y) grid[item] = '*' for dx in range(-5, 20): for dy in range(-20, 20): neighbor = Coordinate(item.x + dx, item.y + dy) if neighbor in seen: continue queue.put(neighbor) seen.add(neighbor) for x in range(self.target.min_x, self.target.max_x + 1): for y in range(self.target.min_y, self.target.max_y + 1): grid[Coordinate(x, y)] = '#' grid[Coordinate(0, 0)] = '!' # grid.to_grid().print(not_found='.') self.max_y = max_y self.hitting_velocity_count = len(grid.find(lambda _: _ in '*#'))
def _get_keycode(self, keypad: Grid[str]): coordinate = Coordinate(1, 1, system=CoordinateSystem.X_RIGHT_Y_DOWN) result = "" for line in self.lines: for character in line: new_coordinate = coordinate.move(character) if keypad[new_coordinate] is not None: coordinate = new_coordinate result += keypad[coordinate] return result
def part2(self): result = 0 for x, y in self.bounding_box: coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) total_distance = sum( coordinate.manhattan(i) for i in self.coordinates) if total_distance < 10000: result += 1 print("Part 2:", result)
def part2(self): self.grid.clear() self.grid[Coordinate(0, 0)] = True self._paint() print("Part 2:") self.grid.to_grid().print(key=lambda is_white: '#' if is_white else ' ')
def __init__(self, file_name): self.instructions = Input(file_name).lines() self.grid: InfiniteGrid[bool] = InfiniteGrid[bool]() reference_coordinate = Coordinate(0, 0) for instruction in self.instructions: coordinate = reference_coordinate index = 0 while index < len(instruction): if instruction[index] == 'n': if instruction[index + 1] == 'w': coordinate = coordinate.left().up() elif instruction[index + 1] == 'e': coordinate = coordinate.up() index += 2 elif instruction[index] == 's': if instruction[index + 1] == 'w': coordinate = coordinate.down() elif instruction[index + 1] == 'e': coordinate = coordinate.right().down() index += 2 elif instruction[index] == 'w': coordinate = coordinate.left() index += 1 elif instruction[index] == 'e': coordinate = coordinate.right() index += 1 if coordinate in self.grid: self.grid[coordinate] = not self.grid[coordinate] else: self.grid[coordinate] = True
def to_grid(self) -> Grid[T]: data = {} min_x = min_y = 4294967296 max_x = max_y = -4294967296 for coordinate, item in self._data.items(): x = coordinate.x y = coordinate.y if coordinate.system == CoordinateSystem.X_RIGHT_Y_DOWN: y = -y coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_UP) data[coordinate] = item min_x = min(min_x, x) max_x = max(max_x, x) min_y = min(min_y, y) max_y = max(max_y, y) width = max_x - min_x + 1 height = max_y - min_y + 1 result = Grid[T](width, height) for coordinate, item in data.items(): result[coordinate.x - min_x, max_y - coordinate.y] = item return result
def fold_func(coordinate: Coordinate): if coordinate.x > value: return Coordinate(x=-(coordinate.x - value) + value, y=coordinate.y, system=coordinate.system) else: return coordinate
def _mutate_recursive(cls, grids: Dict[int, Grid[str]]) -> Dict[int, Grid[str]]: result: Dict[int, Grid[str]] = {} min_level = 2**24 max_level = -2**24 for key, grid in grids.items(): result[key] = grid min_level = min(min_level, key) max_level = max(max_level, key) if len(grids[min_level].find('#')) > 0: min_level -= 1 result[min_level] = grids[0].copy() result[min_level].fill('.') if len(grids[max_level].find('#')) > 0: max_level += 1 result[max_level] = grids[0].copy() result[max_level].fill('.') for key, grid in result.items(): below = grids[key + 1] if key + 1 in grids else None above = grids[key - 1] if key - 1 in grids else None current = result[key] next_grid = result[key].copy() for x in range(5): for y in range(5): coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) bug_count = cls._get_bug_count(coordinate, current, above, below) is_infected = cls._is_infected(current[coordinate], bug_count) next_grid[coordinate] = '#' if is_infected else '.' result[key] = next_grid return result
def _asteroids_visible_from(self, base: Coordinate): seen_angles = {} asteroid: Coordinate for asteroid in self.asteroids: dx = asteroid.x - base.x dy = asteroid.y - base.y if dx == dy == 0: continue divider = gcd(dx, dy) dx = dx // divider dy = dy // divider t = (dx, dy,) # Asteroid is closer than what we have if t in seen_angles and seen_angles[t] > divider: seen_angles[t] = divider elif t not in seen_angles: seen_angles[t] = divider result = [] for position, divider in seen_angles.items(): coordinate = Coordinate( position[0] * divider + base.x, position[1] * divider + base.y, system=CoordinateSystem.X_RIGHT_Y_DOWN ) result.append(coordinate) return result
def part2(self): new_grid: InfiniteGrid[int] = self._grid.copy() for mult_x in range(0, 5): for mult_y in range(0, 5): if mult_x == 0 and mult_y == 0: continue for x in range(self.size): for y in range(self.size): if mult_y == 0: previous_x = (mult_x - 1) * self.size + x previous_y = y else: previous_x = mult_x * self.size + x previous_y = (mult_y - 1) * self.size + y value = new_grid[previous_x, previous_y] + 1 if value > 9: value = 1 new_grid[mult_x * self.size + x, mult_y * self.size + y] = value print() new_end = Coordinate(x=new_grid.max_x, y=new_grid.max_y, system=CoordinateSystem.X_RIGHT_Y_DOWN) path = self.find_path(new_grid, self.start, new_end) result = sum(new_grid[x] for x in path if x != self.start) print("Part 2:", result)
def part2(self): grid: InfiniteGrid[bool] = self.grid.copy() cleaner = Turtle(direction=TurtleDirection.NORTH, coordinate=Coordinate( 0, 0, system=CoordinateSystem.X_RIGHT_Y_DOWN)) result = 0 for _ in range(10_000_000): node_type = grid[cleaner.coordinate] if node_type is None: node_type = NodeType.Clean if node_type == NodeType.Clean: cleaner = cleaner.turn_left() grid[cleaner.coordinate] = NodeType.Weakened elif node_type == NodeType.Weakened: result += 1 grid[cleaner.coordinate] = NodeType.Infected elif node_type == NodeType.Infected: cleaner = cleaner.turn_right() grid[cleaner.coordinate] = NodeType.Flagged else: cleaner = cleaner.turn_left(2) grid[cleaner.coordinate] = NodeType.Clean cleaner = cleaner.forward()
def part2(self): grid = InfiniteGrid[bool]() santa = Coordinate(0, 0) robot_santa = santa grid[santa] = True for index, character in enumerate(self.path): if index % 2 == 0: santa = santa.move(character) grid[santa] = True else: robot_santa = robot_santa.move(character) grid[robot_santa] = True result = len(grid.find(True)) print("Part 2:", result)
def cut(self, bounding_box: BoundingBox) -> Grid[T]: new_width = bounding_box.max_x - bounding_box.min_x + 1 new_height = bounding_box.max_y - bounding_box.min_y + 1 new_grid = Grid[T](new_width, new_height) for row in range(new_height): for col in range(new_width): old_coordinate = Coordinate( col + bounding_box.min_x, row + bounding_box.min_y, system=CoordinateSystem.X_RIGHT_Y_DOWN) new_coordinate = Coordinate( col, row, system=CoordinateSystem.X_RIGHT_Y_DOWN) new_grid[new_coordinate] = self[old_coordinate] return new_grid
def fill(self, item: T): for row in range(self.height): for col in range(self.width): coordinate = Coordinate(col, row, system=CoordinateSystem.X_RIGHT_Y_DOWN) self._data[coordinate] = item
def __init__(self, file_name): line = Input(file_name).line() start = Coordinate(0, 0) grid = self._build_map(start, line) walkable = ['X', '.', '|', '-'] self.flood_map = grid.flood_map(start, *walkable)
def part1(self): end = Coordinate(31, 39, system=CoordinateSystem.X_RIGHT_Y_DOWN) path = self.grid.find_path(self.start, end, True) result = len(path) - 1 print("Part 1:", result)
def part1(self): self._map_out_grid() start = Coordinate(0, 0) oxygen = self.grid.find(Tile.OXYGEN)[0] graph = self.grid.to_graph(Tile.EMPTY, Tile.OXYGEN) # -1 is because path includes start and they want "steps to oxygen" result = len(graph.find_path(start, oxygen, CoordinateHeuristic())) - 1 print("Part 1:", result)
def part1(self): excluded_coordinates: Set[Coordinate] = set() for x in range(self.bounding_box.min_x, self.bounding_box.max_x + 1): coordinate = Coordinate(x, self.bounding_box.min_y, system=CoordinateSystem.X_RIGHT_Y_DOWN) value = self.grid[coordinate] if value is not None: excluded_coordinates.add(value) coordinate = Coordinate(x, self.bounding_box.max_y, system=CoordinateSystem.X_RIGHT_Y_DOWN) value = self.grid[coordinate] if value is not None: excluded_coordinates.add(value) for y in range(self.bounding_box.min_y, self.bounding_box.max_y + 1): coordinate = Coordinate(self.bounding_box.min_x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) value = self.grid[coordinate] if value is not None: excluded_coordinates.add(value) coordinate = Coordinate(self.bounding_box.max_x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) value = self.grid[coordinate] if value is not None: excluded_coordinates.add(value) result = 0 for coordinate in self.coordinates: if coordinate in excluded_coordinates: continue result = max(result, len(self.grid.find(coordinate))) print("Part 1:", result)
def _to_coordinate(position) -> Coordinate: """ We make the assumption that the grid is x right y down if they're just passing in coordinates. However, the user can use any coordinate system they want in an infinite grid. """ if isinstance(position, tuple): x, y = position position = Coordinate(int(x), int(y), system=CoordinateSystem.X_RIGHT_Y_DOWN) return position
def __init__(self, file_name): line = Input(file_name).line() self.furthest_distance = 0 self.coordinate = Coordinate(0, 0) for step in line.split(","): if step == 'n': self.coordinate = self.coordinate.up() elif step == 's': self.coordinate = self.coordinate.down() elif step == 'ne': self.coordinate = self.coordinate.right() elif step == 'sw': self.coordinate = self.coordinate.left() elif step == 'nw': self.coordinate = self.coordinate.left().up() elif step == 'se': self.coordinate = self.coordinate.right().down() self.furthest_distance = max(self.furthest_distance, self._distance(self.coordinate))
def _mutate(grid: Grid[str], moving: Moving) -> (bool, Grid[str]): result = grid.copy() max_x = grid.max_x max_y = grid.max_y locations = grid.find('>') if moving == Moving.EAST else grid.find('v') something_changed = False for location in locations: test = location.right( ) if moving == Moving.EAST else location.down() if test.x > max_x: test = Coordinate(0, test.y, system=test.system) if test.y > max_y: test = Coordinate(test.x, 0, system=test.system) if grid[test] == '.': result[test] = '>' if moving == Moving.EAST else 'v' result[location] = '.' something_changed = True return something_changed, result
def _to_coordinates(line_string): line = line_string.split(',') current: Coordinate = Coordinate(0, 0) coordinates = [] for element in line: direction = element[0] count = int(element[1:]) for _ in range(count): current = current.move(direction) coordinates.append(current) return coordinates
def _neighbors(coordinate: Coordinate) -> Set[Coordinate]: return { coordinate.left().up(), coordinate.up(), coordinate.down(), coordinate.right().down(), coordinate.left(), coordinate.right() }
def _enhance(self, grid: InfiniteGrid[str], fill: str): result = InfiniteGrid[str]() min_x = grid.min_x - 2 max_x = grid.max_x + 3 min_y = grid.min_y - 2 max_y = grid.max_y + 3 for x in range(min_x, max_x): for y in range(min_y, max_y): coordinate = Coordinate(x, y, system=CoordinateSystem.X_RIGHT_Y_DOWN) if coordinate not in grid: grid[coordinate] = fill neighbors = coordinate.neighbors8() for neighbor in neighbors: if neighbor not in grid: grid[neighbor] = fill bin_str = f"{grid[coordinate.up().left()]}" \ f"{grid[coordinate.up()]}" \ f"{grid[coordinate.up().right()]}" \ f"{grid[coordinate.left()]}" \ f"{grid[coordinate]}" \ f"{grid[coordinate.right()]}" \ f"{grid[coordinate.down().left()]}" \ f"{grid[coordinate.down()]}" \ f"{grid[coordinate.down().right()]}" \ .replace('.', '0') \ .replace('#', '1') index = int(bin_str, 2) result[coordinate] = self._enhancement[index] return result
def neighbor_count(self, coordinate: Coordinate, test: Union[T, Callable]) -> int: result = 0 for neighbor in coordinate.neighbors(): if neighbor not in self: continue item = self._data[neighbor] if callable(test): if test(item): result += 1 elif item == test: result += 1 return result
def part1(self): grid: InfiniteGrid[bool] = self.grid.copy() cleaner = Turtle(direction=TurtleDirection.NORTH, coordinate=Coordinate( 0, 0, system=CoordinateSystem.X_RIGHT_Y_DOWN)) result = 0 for _ in range(10_000): clean = grid[cleaner.coordinate] in [None, NodeType.Clean] cleaner = cleaner.turn_left() if clean else cleaner.turn_right() grid[cleaner. coordinate] = NodeType.Infected if clean else NodeType.Clean result += 1 if clean else 0 cleaner = cleaner.forward()
def __init__(self, file_name): self.favorite_number = Input(file_name).int() def _grid(_: MagicGrid[bool], coordinate: Coordinate) -> bool: x = coordinate.x y = coordinate.y if x < 0 or y < 0: return False result = x * x + 3 * x + 2 * x * y + y + y * y result += self.favorite_number return bin(result).count("1") % 2 == 0 self.grid = MagicGrid[bool](_grid) self.start = Coordinate(1, 1, system=CoordinateSystem.X_RIGHT_Y_DOWN)