from tools.aoc import AOCDay from tools.coordinate import Coordinate from typing import Any, List, Union class Cube: coord_ulb: Coordinate # min_coord / up left back coord_urb: Coordinate coord_ulf: Coordinate coord_urf: Coordinate coord_dlb: Coordinate coord_drb: Coordinate coord_dlf: Coordinate coord_drf: Coordinate # max_coord / down right front def __init__(self, min_coord: Coordinate, max_coord: Coordinate): assert max_coord >= min_coord, "invalid cube spec: %s < %s" % (min_coord, max_coord) self.coord_ulb = min_coord self.coord_drf = max_coord self.coord_urb = Coordinate(self.coord_ulb.x, self.coord_drf.y, self.coord_ulb.z) self.coord_ulf = Coordinate(self.coord_ulb.x, self.coord_ulb.y, self.coord_drf.z) self.coord_urf = Coordinate(self.coord_ulb.x, self.coord_drf.y, self.coord_drf.z) self.coord_dlb = Coordinate(self.coord_drf.x, self.coord_ulb.y, self.coord_ulb.z) self.coord_drb = Coordinate(self.coord_drf.x, self.coord_drf.y, self.coord_ulb.z) self.coord_dlf = Coordinate(self.coord_drf.x, self.coord_ulb.y, self.coord_drf.z) def getSize(self): return (self.coord_drf.x - self.coord_ulb.x) * (self.coord_drf.y - self.coord_ulb.y) * (self.coord_drf.z - self.coord_ulb.z) def intersect(self, other: 'Cube') -> Union['Cube', None]: """ if not other.coord_drf >= self.coord_ulb and not other.coord_ulb <= self.coord_drf: return None if other.coord_ulb <= self.coord_ulb and other.coord_drf >= self.coord_drf: return Cube(self.coord_ulb, self.coord_drf) if other.coord_drf <= self.coord_drf and other.coord_ulb >= self.coord_ulb: return Cube(other.coord_ulb, other.coord_drf) """ try: return Cube( Coordinate( max(self.coord_ulb.x, other.coord_ulb.x), max(self.coord_ulb.y, other.coord_ulb.y), max(self.coord_ulb.z, other.coord_ulb.z) ), Coordinate( min(self.coord_drf.x, other.coord_drf.x), min(self.coord_drf.y, other.coord_drf.y), min(self.coord_drf.z, other.coord_drf.z), ) ) except AssertionError: return None def __str__(self): return "Cube(<%d,%d,%d>;<%d,%d,%d>)" % ( self.coord_ulb.x, self.coord_ulb.y, self.coord_ulb.z, self.coord_drf.x, self.coord_drf.y, self.coord_drf.z, ) def __repr__(self): return self.__str__() def addCube(cubes: List[Cube], state: bool, new_cube: Cube) -> List[Cube]: new_cubes = [] for old_cube in cubes: intersect_cube = old_cube.intersect(new_cube) if not intersect_cube: new_cubes.append(old_cube) else: if intersect_cube.coord_ulb > old_cube.coord_ulb and intersect_cube.coord_drf < old_cube.coord_drf: # somewhere in the middle # => 6 new cubes (2*3x3, 2*1x3, 2*1x1 print("intersection in the middle of old_cube: %s is within %s (intersect: %s)" % (new_cube, old_cube, intersect_cube)) new_cubes.append( Cube( old_cube.coord_ulb, Coordinate(old_cube.coord_ulb.x, old_cube.coord_urb.y, intersect_cube.coord_ulb.z - 1) ) ) new_cubes.append( Cube( Coordinate(old_cube.coord_ulf.x, old_cube.coord_urf.y, intersect_cube.coord_drf.z + 1), old_cube.coord_drf ) ) new_cubes.append( Cube( Coordinate(old_cube.coord_ulb.x, old_cube.coord_ulb.y, intersect_cube.coord_ulb.z), Coordinate(old_cube.coord_dlf.x, intersect_cube.coord_dlf.y - 1, intersect_cube.coord_drf.z) ) ) new_cubes.append( Cube( Coordinate(old_cube.coord_urb.x, intersect_cube.coord_urb.y + 1, intersect_cube.coord_urb.z), Coordinate(old_cube.coord_drf.x, old_cube.coord_drf.y, intersect_cube.coord_drf.z) ) ) new_cubes.append( Cube( Coordinate(old_cube.coord_ulb.x, intersect_cube.coord_ulb.y, intersect_cube.coord_ulb.z), Coordinate(intersect_cube.coord_urf.x - 1, intersect_cube.coord_urf.y, intersect_cube.coord_urf.z) ) ) new_cubes.append( Cube( Coordinate(intersect_cube.coord_dlb.x + 1, intersect_cube.coord_dlb.y, intersect_cube.coord_dlb.z), Coordinate(intersect_cube.coord_drf.x, intersect_cube.coord_drf.y, old_cube.coord_drf.z) ) ) else: print("%s intersecting with %s => %s" % (old_cube, new_cube, intersect_cube)) pass if state: new_cubes.append(new_cube) return new_cubes class Day(AOCDay): test_solutions_p1 = [590784] test_solutions_p2 = [2758514936282235] def getCubeList(self, part1: bool = False) -> List[Cube]: cubes = [] for line in self.getInput(): state, c = line.split() co = c.split(",") _, xco = co[0].split("=") _, yco = co[1].split("=") _, zco = co[2].split("=") minX, maxX = map(int, xco.split("..")) minY, maxY = map(int, yco.split("..")) minZ, maxZ = map(int, zco.split("..")) new_cube = Cube(Coordinate(minX, minY, minZ), Coordinate(maxX, maxY, maxZ)) if not part1 or (new_cube.coord_ulb > Coordinate(-50, -50, -50) and new_cube.coord_drf < Coordinate(50, 50, 50)): print("ADDING", state, new_cube) cubes = addCube(cubes, state == "on", new_cube) print(cubes) return cubes def part1(self) -> Any: on_count = 0 cubes = self.getCubeList(part1=True) for cube in cubes: on_count += cube.getSize() return on_count def part2(self) -> Any: on_count = 0 for cube in self.getCubeList(): on_count += cube.getSize() return on_count