In day 9, we calculate areas in a very large 2-D space
import sys
from collections import deque
from queue import PriorityQueue
import copy
import math
import itertools
def getPoints(stry: str) -> list[str]:
with open(stry) as f:
data = f.read().splitlines()
points=[]
for l in data:
l = l.split(",")
x = int(l[0])
y = int(l[1])
points.append((x,y))
return points
points = getPoints("9.input")
small_points = getPoints("9.small")
Part 1 is straight forward enough: find all the pairs of points, calculate their areas, find the max area. Note becareful of calculating area
def area(pair):
p1, p2 = pair
if p1[0] == p2[0]:
#Case 1 same row
return abs(p1[0]-p2[0])+1
elif p1[1] == p2[1]:
#Case 2 same column:
return abs(p1[1]-p2[1])+1
else:
#Case 3 NW SE
#Case 4 NE SW
return (abs(p1[1]-p2[1])+1)*(abs(p1[0]-p2[0])+1)
print(f"The answer for example in part one is {max([ area(pr) for pr in list(
itertools.combinations(small_points, 2))])}")
print(f"The answer for part one is {max([ area(pr) for pr in list(itertools.combinations(points, 2))])}")
The answer for example in part one is 50 The answer for part one is 4763932976
Part 2 is significantly more difficult. As I did on Day 10, I borrow heavily from HyperNeutrino’s approach. He introduces two key optimizations: grid compression and a 2-D prefix sum array.
His basic procedure, as outlined in the following video, is:
In the code below, HyperNeutrino uses grid compression to keep the memory usage manageable.
def compressGrid(points: list[(int,int)]) -> list[list[int]]:
xs = sorted({ x for x,_ in points })
ys = sorted({ y for _,y in points })
grid = [[0]*(len(xs) * 2 - 1) for _ in range(len(ys)*2-1)]
for (x1,y1), (x2,y2) in zip(points, points[1:]+points[:1]):
cx1,cx2 = sorted([xs.index(x1) * 2, xs.index(x2) * 2])
cy1,cy2 = sorted([ys.index(y1) * 2, ys.index(y2) * 2])
for cx in range(cx1,cx2+1):
for cy in range(cy1,cy2+1):
grid[cy][cx]=1
return grid
grid = compressGrid(points)
small_grid = compressGrid(small_points)
for row in small_grid:
print("".join([str(n) for n in row]))
0011111 0010001 1110001 1000001 1111101 0000101 0000111
Flood fill is used to mark areas enclosed by red and green tiles.
def flood(grid: list[list[int]])-> list[list[int]]:
outside={(-1,-1)}
q = deque(outside)
while q :
tx, ty = q.popleft()
for nx,ny in [(tx-1,ty), (tx+1,ty), (tx,ty-1),(tx,ty+1)]:
if nx < -1 or ny < -1 or nx > len(grid[0]) or ny > len(grid): continue
if 0 <= nx < len(grid[0]) and 0 <= ny < len(grid) and grid[ny][nx]==1: continue
if (nx,ny) in outside: continue
outside.add((nx,ny))
q.append((nx,ny))
for x in range(len(grid[0])):
for y in range(len(grid)):
if (x,y) not in outside:
grid[y][x]=1
return grid
grid=flood(grid)
small_grid=flood(small_grid)
for row in small_grid:
print("".join([str(n) for n in row]))
0011111 0011111 1111111 1111111 1111111 0000111 0000111
The prefix summary array is an efficient way to calculate the value of red and green tiles without having to iterate over all the elements repetitively.
def buildPSA(grid: list[list[int]]) -> list[list[int]]:
psa = [[0] * len(row) for row in grid]
for y in range(len(psa)):
for x in range(len(psa[0])):
left = psa[y][x- 1] if x > 0 else 0
top = psa[y-1][x] if y > 0 else 0
topleft = psa[y - 1][x - 1] if x > 0 < y else 0
psa[y][x] = left + top - topleft + grid[y][x]
return psa
psa = buildPSA(grid)
s_psa = buildPSA(small_grid)
for row in s_psa:
print("".join([f"{n:02d}|" for n in row]))
00|00|01|02|03|04|05| 00|00|02|04|06|08|10| 01|02|05|08|11|14|17| 02|04|08|12|16|20|24| 03|06|11|16|21|26|31| 03|06|11|16|22|28|34| 03|06|11|16|23|30|37|
def valid(psa, x1, y1, x2, y2,xs,ys):
cx1, cx2 = sorted([xs.index(x1)*2, xs.index(x2)*2])
cy1, cy2 = sorted([ys.index(y1)*2, ys.index(y2)*2])
left = psa[cy2][cx1 - 1] if cx1 > 0 else 0
top = psa[cy1-1][cx2] if cy1 > 0 else 0
topleft = psa[cy1-1][cx1 - 1] if (cx1 > 0 < cy1) else 0
count = psa[cy2][cx2] - left - top + topleft
return count == (abs(cx2 - cx1) + 1) * (abs(cy2 - cy1) + 1)
sxs = sorted({ x for x,_ in small_points })
sys = sorted({ y for _,y in small_points })
areas = [((x1,y1,x2,y2),(abs(x2 - x1) + 1) * (abs(y2 - y1) + 1)) for i, (x1, y1) in enumerate(small_points) for x2, y2 in small_points[:i]]
valid_areas = [((x1,y1,x2,y2),(abs(x2 - x1) + 1) * (abs(y2 - y1) + 1)) for i, (x1, y1) in enumerate(small_points) for x2, y2 in small_points[:i] if valid(s_psa,x1,y1,x2,y2,sxs,sys)]
invalid_areas = [((x1,y1,x2,y2),(abs(x2 - x1) + 1) * (abs(y2 - y1) + 1)) for i, (x1, y1) in enumerate(small_points) for x2, y2 in small_points[:i] if not valid(s_psa,x1,y1,x2,y2,sxs,sys)]
small_ans1 = max((abs(x2 - x1) + 1) * (abs(y2 - y1) + 1) for i, (x1, y1) in enumerate(small_points) for x2, y2 in small_points[:i] if valid(s_psa,x1,y1,x2,y2,sxs,sys))
print(f"The areas are \n{areas}")
print(f"The valid areas are \n{valid_areas}")
print(f"The invalid areas are \n{invalid_areas}")
print(f"The answer for part two example prompt is {small_ans1}")
xs = sorted({ x for x,_ in points })
ys = sorted({ y for _,y in points })
ans1 = max((abs(x2 - x1) + 1) * (abs(y2 - y1) + 1) for i, (x1, y1) in enumerate(points) for x2, y2 in points[:i] if valid(psa,x1, y1, x2, y2,xs,ys))
print(f"The answer for part two is {ans1}")
The areas are [((11, 1, 7, 1), 5), ((11, 7, 7, 1), 35), ((11, 7, 11, 1), 7), ((9, 7, 7, 1), 21), ((9, 7, 11, 1), 21), ((9, 7, 11, 7), 3), ((9, 5, 7, 1), 15), ((9, 5, 11, 1), 15), ((9, 5, 11, 7), 9), ((9, 5, 9, 7), 3), ((2, 5, 7, 1), 30), ((2, 5, 11, 1), 50), ((2, 5, 11, 7), 30), ((2, 5, 9, 7), 24), ((2, 5, 9, 5), 8), ((2, 3, 7, 1), 18), ((2, 3, 11, 1), 30), ((2, 3, 11, 7), 50), ((2, 3, 9, 7), 40), ((2, 3, 9, 5), 24), ((2, 3, 2, 5), 3), ((7, 3, 7, 1), 3), ((7, 3, 11, 1), 15), ((7, 3, 11, 7), 25), ((7, 3, 9, 7), 15), ((7, 3, 9, 5), 9), ((7, 3, 2, 5), 18), ((7, 3, 2, 3), 6)] The valid areas are [((11, 1, 7, 1), 5), ((11, 7, 11, 1), 7), ((9, 7, 11, 1), 21), ((9, 7, 11, 7), 3), ((9, 5, 7, 1), 15), ((9, 5, 11, 1), 15), ((9, 5, 11, 7), 9), ((9, 5, 9, 7), 3), ((2, 5, 9, 5), 8), ((2, 3, 9, 5), 24), ((2, 3, 2, 5), 3), ((7, 3, 7, 1), 3), ((7, 3, 11, 1), 15), ((7, 3, 9, 5), 9), ((7, 3, 2, 5), 18), ((7, 3, 2, 3), 6)] The invalid areas are [((11, 7, 7, 1), 35), ((9, 7, 7, 1), 21), ((2, 5, 7, 1), 30), ((2, 5, 11, 1), 50), ((2, 5, 11, 7), 30), ((2, 5, 9, 7), 24), ((2, 3, 7, 1), 18), ((2, 3, 11, 1), 30), ((2, 3, 11, 7), 50), ((2, 3, 9, 7), 40), ((7, 3, 11, 7), 25), ((7, 3, 9, 7), 15)] The answer for part two example prompt is 24 The answer for part two is 1501292304