import matrix import shared from pprint import pprint from dataclasses import dataclass from typing import Set, Tuple from functools import cached_property from mpl_toolkits.mplot3d import Axes3D import numpy as np import matplotlib.pyplot as plt @dataclass class Cube: x: int y: int z: int neighbor_coords: Set[Tuple[int,int,int]] = None @cached_property def xyz(self): return (self.x,self.y,self.z) def set_neighbor_coords(self): self.neighbor_coords = get_neighbors(self.x,self.y,self.z) def get_neighbors(x,y,z): # Generate the six neighbor_coords # Look at a die 6 to the left, 2 on top, one on right offsets = ( (1,0,0), # 1 (0,0,1), # 2 (0,-1,0),# 3 (0,1,0), # 4 (0,0,-1),# 5 (-1,0,0) # 6 ) return set([(x+o[0], y+o[1], z+o[2]) for o in offsets]) def get_flat_neighbors(x,y,z): # Generate the six neighbor_coords # Look at a die 6 to the left, 2 on top, one on right # ignore top and bottom offsets = ( (1,0,0), # 1 #(0,0,1), # 2 (0,-1,0),# 3 (0,1,0), # 4 #(0,0,-1),# 5 (-1,0,0) # 6 ) return set([(x+o[0], y+o[1], z+o[2]) for o in offsets]) def get_flat_neighbors_from(x,y,z, f): return get_flat_neighbors(x,y,z).intersection(f) # @shared.profile def part1(rows): cubes = {} for idx, (x,y,z) in enumerate(rows): cubes[(x,y,z)] = {'n':get_neighbors(x,y,z), 'not_me': tuple(map(tuple,rows[:idx] + rows[idx+1:]))} potential = len(cubes) * 6 for coords, cube in cubes.items(): for other in cube['not_me']: if other in cube['n']: potential -= 1 print(potential) def surface_area(rows): cubes = {} maxX,maxY,maxZ = 0,0,0 for idx, (x,y,z) in enumerate(rows): cubes[(x,y,z)] = {'n':get_neighbors(x,y,z), 'not_me': set(map(tuple,rows[:idx] + rows[idx+1:]))} maxX = max(maxX,x) maxY = max(maxY,y) maxZ = max(maxZ,z) _cubes = frozenset(cubes.keys()) potential = len(cubes) * 6 for coords, cube in cubes.items(): for other in cube['not_me']: if other in cube['n']: potential -= 1 return potential, maxX,maxY,maxZ, cubes, _cubes def part2(rows): potential, maxX, maxY,maxZ,cubes,_cubes = surface_area(rows) air = set() for x in range(0,maxX+1): for y in range(0,maxY+1): for z in range(0,maxZ+1): if (x,y,z) not in _cubes: air.add((x,y,z)) air -= _cubes # Remove all lava from air all_air_count = len(air) all_lava_count = len(_cubes) possible_count = (maxX+1)*(maxY+1)*(maxZ+1) print("all_air: ", all_air_count) print("all_lava:", all_lava_count) print("possible:", possible_count) print(maxZ,maxY,maxX) print() air_nx = {} for a in air: neighbors = get_neighbors(*a) air_nx[a] = { 'lava':neighbors & _cubes or None, 'air': neighbors & air or None } # loop row by row inside = [] for z in range(0,maxZ+1): seen = set() seen.add((0,0,z)) for y in range(0,maxY+1): for x in range(0,maxX+1): xyz = (x,y,z) if xyz in _cubes: #print(xyz,'is cube') continue ns = get_flat_neighbors_from(x,y,z, air) for neigh in ns: if neigh in seen and neigh not in _cubes: seen.add(xyz) this_level = air_in_row(air, z) print(this_level, seen) print(len(this_level), len(seen)) inside.extend([x for x in this_level if x not in seen]) #print() #print(inside) #print() actually_inside = [] for i in inside: ns = get_neighbors(*i) # check for surrounded 100% by rock rock_count = 0 for n in ns: if n in _cubes: rock_count +=1 if rock_count == 6: print("in rock") actually_inside.append(i) continue #check for surrounded 100% by air air_count = 0 for n in ns: if n in air: air_count +=1 print("in air") break actually_inside.append(i) #print(actually_inside) tot, _, _, _, _, _ = surface_area(actually_inside) print(tot) print(potential - tot) shared.render_cubes(maxX,maxY,maxZ, [x for x in actually_inside]) def air_in_row(air, z): x = [] for a in air: if a[2] == z: x.append(a) return x def main(): rows = [list(map(int,row.split(","))) for row in shared.load_rows(18)] with shared.elapsed_timer() as elapsed: part1(rows) print("🕒", elapsed()) #rows = [map(int,row.split(",")) for row in shared.load_rows(18)] with shared.elapsed_timer() as elapsed: part2(rows) print("🕒", elapsed()) if __name__ == "__main__": main()