from collections import defaultdict from tools.aoc import AOCDay from tools.coordinate import Coordinate from typing import Any, Generator, 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 + 1) * (self.coord_drf.y - self.coord_ulb.y + 1) * (self.coord_drf.z - self.coord_ulb.z + 1) ) def intersect(self, other: 'Cube') -> Union['Cube', None]: intersect_ulr = Coordinate( #max(self.coord_ulb.x, other.coord_ulb.x), self.coord_ulb.x if self.coord_ulb.x > other.coord_ulb.x else other.coord_ulb.x, #max(self.coord_ulb.y, other.coord_ulb.y), self.coord_ulb.y if self.coord_ulb.y > other.coord_ulb.y else other.coord_ulb.y, #max(self.coord_ulb.z, other.coord_ulb.z) self.coord_ulb.z if self.coord_ulb.z > other.coord_ulb.z else other.coord_ulb.z, ) intersect_drf = Coordinate( #min(self.coord_drf.x, other.coord_drf.x), self.coord_drf.x if self.coord_drf.x < other.coord_drf.x else other.coord_drf.x, #min(self.coord_drf.y, other.coord_drf.y), self.coord_drf.y if self.coord_drf.y < other.coord_drf.y else other.coord_drf.y, #min(self.coord_drf.z, other.coord_drf.z), self.coord_drf.z if self.coord_drf.z < other.coord_drf.z else other.coord_drf.z, ) if intersect_ulr <= intersect_drf: return Cube(intersect_ulr, intersect_drf) 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__() class Day(AOCDay): inputs = [ [ (39, "test_input22_1_0"), (590784, "test_input22_1_1"), (570915, "input22") ], [ (2758514936282235, "test_input22_2_0"), (1268313839428137, "input22") ] ] def getCubeList(self, part1: bool = False): 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)): yield state == "on", new_cube def getOnSum(self, part1: bool = False) -> int: cubes = defaultdict(int) for switch_state, this_cube in self.getCubeList(part1=part1): for prior_cube in cubes.copy(): intersect_cube = this_cube.intersect(prior_cube) if intersect_cube: cubes[intersect_cube] -= cubes[prior_cube] if switch_state: cubes[this_cube] = 1 #print(cubes) return sum(cube.getSize() * on_off for cube, on_off in cubes.items()) def part1(self) -> Any: return self.getOnSum(part1=True) def part2(self) -> Any: return self.getOnSum() if __name__ == '__main__': day = Day(22) day.run(verbose=True)