def test_lattice_midpoint(self): self.assertEqual(self.p1.lattice_midpoint( self.p2), LatticePoint(1, -2)) self.assertEqual(self.p2.lattice_midpoint( self.p3), LatticePoint(0, -2)) self.assertEqual(self.p3.lattice_midpoint( self.p4), LatticePoint(-2, 3)) self.assertEqual(self.p4.lattice_midpoint( self.p1), LatticePoint(-1, 3))
class InfiniteGrid(ExpandableGrid): _adj = [ LatticePoint(-1, -1), LatticePoint(0, -1), LatticePoint(1, -1), LatticePoint(-1, 0), LatticePoint(0, 0), LatticePoint(1, 0), LatticePoint(-1, 1), LatticePoint(0, 1), LatticePoint(1, 1) ] def get(self, pt: LatticePoint, default: str = '.') -> Location: if not isinstance(pt, LatticePoint): raise TypeError( f'Grid accessor must be of type Point, type {type(pt)} provided' ) if pt not in self: return Location(pt.x, pt.y, loc_type=Location.OPEN, rep=default) return self.grid[pt.y - self.offset.y][pt.x - self.offset.x] def get_pixel_details(self, pt: LatticePoint, which: int) -> str: rv = '' char = '.' if which % 2 == 0 else '#' for adj in InfiniteGrid._adj: rv += '1' if self.get(pt + adj, char).rep == '#' else '0' return int(rv, 2)
def from_list_of_strings(cls, rows: List[str], wall_char: str = '#', offset: LatticePoint = LatticePoint(0, 0)): """Build a grid from a list of strings of equal length""" bounds = LatticePoint(len(rows[0]), len(rows)) grid = cls.blank(bounds, offset) for x in range(bounds.x): for y in range(bounds.y): is_wall = Location.IMPASSABLE if rows[y][x] == wall_char else Location.OPEN loc = Location(x+offset.x, y+offset.y, is_wall, rows[y][x]) grid[LatticePoint(x+offset.x, y+offset.y)] = loc return grid
def blank(cls, bounds: LatticePoint, offset: LatticePoint = LatticePoint(0, 0)): """Return a blank grid of the given size "bounds" """ grid = [] for y in range(bounds.y): row = [] for x in range(bounds.x): row += [Location(x+offset.x, y+offset.y, Location.OPEN, '.')] grid = [row] + grid return cls(grid, offset=offset)
def copy(self): """This method returns a deep copy of self""" grid = [] for y in range(self.offset.y, self.offset.y+self.height): row = [] for x in range(self.offset.x, self.offset.x+self.width): row += [self[LatticePoint(x, y)].copy()] grid += [row] new_grid = type(self)(grid) new_grid.offset = self.offset return new_grid
def char_positions(self, chars: Iterable[str]) -> Dict[str, List[LatticePoint]]: """ Return a list of points for each character passed in the "chars" list which represents the list of positions in which that character can be found on the grid """ mapping: Dict[str, List[LatticePoint]] = {char: [] for char in chars} for x in range(self.offset.x, self.offset.x+self.width): for y in range(self.offset.y, self.offset.y+self.height): pt = LatticePoint(x, y) if self[pt].rep in mapping: mapping[self[pt].rep].append(pt) return mapping
def overlay(self, other: 'Grid', empty_char: str = '.'): """Overlay the self grid over top of another grid""" if other.bounds[0] not in self or other.bounds[1]-LatticePoint(1, 1) not in self: raise ValueError('Other grid not fully within bounds') # if self.size != other.size or self.offset != other.offset: # raise ValueError('Other grid not fully within bounds') new = self.copy() for loc in other: loc: Location if loc.rep != empty_char: new[loc] = loc return new
def conditional_walls(self, predicate_function: Callable[[LatticePoint], bool], char: str) -> 'Grid': """ This method can be used to add walls based on the results of a function which takes in a Point and returns a boolean for whether there should be a wall at that point """ new = self.copy() for y in range(self.offset.y, self.offset.y+new.height): for x in range(self.offset.x, self.offset.x+new.width): pt = LatticePoint(x, y) if predicate_function(pt): new[pt] = Location(x, y, Location.IMPASSABLE, char) return new
def basin_size(grid: Grid, point: Location, seen: Set) -> int: q = Queue() q.put(point) size = 0 while not q.empty(): pt: Location = q.get() if pt in seen or pt.rep == 9: continue seen.add(pt) size += 1 for adj in pt.get_adjacent_points(lower_bound=LatticePoint(0, 0), upper_bound=grid.size): q.put(grid[adj]) return size
def from_list_of_locations(cls, locations: List[Location]): """Build a grid of sufficient size for all of the included locations""" min_, max_ = LatticePoint( 10**64, 10**64), LatticePoint(-10**64, -10**64) for location in locations: if location.x < min_.x: min_.x = location.x if location.x > max_.x: max_.x = location.x if location.y < min_.y: min_.y = location.y if location.y > max_.y: max_.y = location.y grid = cls.blank(max_-min_+LatticePoint(1, 1), min_) for location in locations: grid[location] = location return grid
def test_floordiv(self): self.assertEqual(self.p1//4, LatticePoint(0, 0)) self.assertEqual(self.p2//1, LatticePoint(1, -5)) self.assertEqual(self.p3//2, LatticePoint(0, 1)) self.assertEqual(self.p4//-1, LatticePoint(3, -5))
def size(self) -> LatticePoint: """This property represents the width and height of the grid""" return LatticePoint(self.width, self.height)
def test_random(self): self.assertTrue(LatticePoint.random(lower_bound=self.p1, upper_bound=LatticePoint(5, 5) ).in_bounds(self.p1, LatticePoint(6, 6)))
def test_add(self): self.assertEqual(self.p1+self.p1, LatticePoint(2, 2)) self.assertEqual(self.p2+self.p3, LatticePoint(1, -3)) self.assertEqual(self.p2+self.p4, LatticePoint(-2, 0)) self.assertEqual(self.p1+self.p3, LatticePoint(1, 3))
def setUp(self): self.p1 = LatticePoint(1, 1) self.p2 = LatticePoint(1, -5) self.p3 = LatticePoint(0, 2) self.p4 = LatticePoint(-3, 5)
def test_down(self): self.assertEqual(self.p1.down(), LatticePoint(1, 0)) self.assertEqual(self.p2.down(), LatticePoint(1, -6)) self.assertEqual(self.p3.down(), LatticePoint(0, 1)) self.assertEqual(self.p4.down(), LatticePoint(-3, 4))
def risk_level(grid: Grid, point: Location) -> int: for adj in point.get_adjacent_points(lower_bound=LatticePoint(0, 0), upper_bound=grid.size): if grid[adj].rep <= point.rep: return 0 return point.rep + 1
def test_right(self): self.assertEqual(self.p1.right(), LatticePoint(2, 1)) self.assertEqual(self.p2.right(), LatticePoint(2, -5)) self.assertEqual(self.p3.right(), LatticePoint(1, 2)) self.assertEqual(self.p4.right(), LatticePoint(-2, 5))
# Written as a solution for Advent of Code 2021 # https://adventofcode.com/2021/day/5 from typing import List from fishpy.geometry import LatticePoint, LineSegment with open('2021/05/input.txt') as f: lines = f.read().rstrip().split('\n') straights: List[LineSegment] = [] diagonals: List[LineSegment] = [] for line in lines: left = line.split(' -> ')[0].split(',') right = line.split(' -> ')[1].split(',') left_point = LatticePoint(int(left[0]), int(left[1])) right_point = LatticePoint(int(right[0]), int(right[1])) segment = LineSegment(left_point, right_point) if left_point.x == right_point.x or left_point.y == right_point.y: straights.append(segment) else: diagonals.append(segment) grid = [[0 for _ in range(1000)] for _ in range(1000)] for segment in straights: for pt in segment.lattice_points_along(): grid[pt.y][pt.x] += 1 count_1 = sum(1 for row in grid for col in row if col >= 2) for segment in diagonals: for pt in segment.lattice_points_along():
def test_sub(self): self.assertEqual(self.p1-self.p1, LatticePoint(0, 0)) self.assertEqual(self.p3-self.p2, LatticePoint(-1, 7)) self.assertEqual(self.p4-self.p2, LatticePoint(-4, 10)) self.assertEqual(self.p1-self.p3, LatticePoint(1, -1))
def setUp(self): self.p1 = LatticePoint(2, 4)
def test_truediv(self): self.assertEqual(self.p3/2, LatticePoint(0, 1)) self.assertEqual(LatticePoint(10, 10)//2, LatticePoint(5, 5)) self.assertRaises(ValueError, LatticePoint.__truediv__, self.p1, 2) self.assertRaises(ValueError, LatticePoint.__truediv__, self.p4, 5)
def test_mul(self): self.assertEqual(self.p1*3, LatticePoint(3, 3)) self.assertEqual(self.p2*-1, LatticePoint(-1, 5)) self.assertEqual(self.p3*2, LatticePoint(0, 4)) self.assertRaises(TypeError, LatticePoint.__mul__, self.p4, 0.5)
def test_neg(self): self.assertEqual(-self.p1, LatticePoint(-1, -1)) self.assertEqual(-self.p2, LatticePoint(-1, 5)) self.assertEqual(-self.p3, LatticePoint(0, -2)) self.assertEqual(-self.p4, LatticePoint(3, -5))
def __init__(self, grid: List[List[Location]], offset: LatticePoint = LatticePoint(0, 0)): self.grid = grid self.offset = offset self._iter = LatticePoint(0, 0)
def test_up(self): self.assertEqual(self.p1.up(), LatticePoint(1, 2)) self.assertEqual(self.p2.up(), LatticePoint(1, -4)) self.assertEqual(self.p3.up(), LatticePoint(0, 3)) self.assertEqual(self.p4.up(), LatticePoint(-3, 6))
def test_abs(self): self.assertEqual(abs(-self.p1), LatticePoint(1, 1)) self.assertEqual(abs(self.p2), LatticePoint(1, 5)) self.assertEqual(abs(-self.p3), LatticePoint(0, 2)) self.assertEqual(abs(self.p4), LatticePoint(3, 5))
def test_left(self): self.assertEqual(self.p1.left(), LatticePoint(0, 1)) self.assertEqual(self.p2.left(), LatticePoint(0, -5)) self.assertEqual(self.p3.left(), LatticePoint(-1, 2)) self.assertEqual(self.p4.left(), LatticePoint(-4, 5))
from typing import Callable from fishpy.geometry import LatticePoint, Vector2D from fishpy.geometry.vector2d import DOWN, RIGHT, UP direction_map = { 'up': UP, 'down': DOWN, 'forward': RIGHT, } with open('2021/02/input.txt') as f: directions = f.read().rstrip().split('\n') position = LatticePoint(0, 0) get_vector: Callable[[str], Vector2D] = lambda string: direction_map[ string.split()[0]] * int(string.split()[1]) for entry in map(get_vector, directions): position += entry print(f'Final depth of first half: {position.x * (-position.y)}') aim = 0 position = LatticePoint(0, 0) for entry in directions: direction, scale = tuple(entry.split()) if direction == 'forward': position += RIGHT * int(scale) position += DOWN * aim * int(scale) if direction == 'up':
flashed: Dict[Location, bool] = {pt: False for pt in grid} for loc in grid: loc: Location loc.rep += 1 while True: positions = grid.char_positions([10, 11, 12, 13, 14, 15, 16]) flattened = [ grid[position] for value in positions for position in positions[value] if not flashed[grid[position]] ] if not flattened: break for loc in flattened: flashed[loc] = True for adj in loc.get_adjacent_points(diagonals=True, lower_bound=LatticePoint(0, 0), upper_bound=grid.size): grid[adj].rep += 1 if sum(flashed.values()) == len(flashed): # Raise iterations to 230 to get answer to part 2 print(i) break for loc in flashed: if flashed[loc]: flashes += 1 loc.rep = 0 print(flashes)