advent-of-code/2022/python/day15.py

319 lines
7.8 KiB
Python
Raw Permalink Normal View History

2022-12-15 06:17:19 +00:00
import matrix
import math
2022-12-15 18:02:54 +00:00
import sys
2022-12-15 06:17:19 +00:00
from pprint import pprint
import shared
from scanf import scanf
2022-12-15 19:33:20 +00:00
from typing import Optional, List, Tuple
2022-12-15 06:17:19 +00:00
from dataclasses import dataclass
2022-12-15 19:33:20 +00:00
from collections import defaultdict
2022-12-15 16:32:45 +00:00
2022-12-17 03:07:17 +00:00
def cityblock(y1, x1, y2, x2):
return abs(y2 - y1) + abs(x2 - x1)
2022-12-15 06:17:19 +00:00
@dataclass
class Sensor:
sX: int
sY: int
bX: int
bY: int
2022-12-17 03:07:17 +00:00
limit: Tuple[int, int] = (0, 0)
2022-12-15 18:02:54 +00:00
_d: int = None
2022-12-17 03:07:17 +00:00
_edges: List[Tuple[int, int]] = None
_border: List[Tuple[int, int]] = None
2022-12-15 19:33:20 +00:00
def __str__(self):
2022-12-17 03:07:17 +00:00
return (
f"Sensor(sX={self.sX}, sY={self.sY}, bX={self.bX},"
f"bY={self.bY}, d={self._d}, edges={len(self._edges)}, borders={len(self._borders)})"
)
2022-12-15 06:17:19 +00:00
@property
def s(self):
2022-12-15 16:32:45 +00:00
return (self.sY, self.sX)
2022-12-15 06:17:19 +00:00
@property
def b(self):
2022-12-15 16:32:45 +00:00
return (self.bY, self.bX)
2022-12-15 06:17:19 +00:00
@property
def distance(self):
2022-12-15 18:02:54 +00:00
if self._d is None:
2022-12-17 03:07:17 +00:00
self._d = cityblock(self.sY, self.sX, self.bY, self.bX)
2022-12-15 18:02:54 +00:00
return self._d
2022-12-15 06:17:19 +00:00
2022-12-15 16:32:45 +00:00
def distance_to(self, bY, bX):
2022-12-17 03:07:17 +00:00
return cityblock(self.sY, self.sX, bY, bX)
2022-12-15 06:17:19 +00:00
2022-12-15 15:08:01 +00:00
def on_line(self, y):
2022-12-17 03:07:17 +00:00
midpoint = (y, self.s[1])
d = self.distance_to(*midpoint)
2022-12-15 15:08:01 +00:00
if d > self.distance:
return []
2022-12-15 16:32:45 +00:00
need = self.distance - d
2022-12-15 15:08:01 +00:00
2022-12-15 16:32:45 +00:00
start = (y, midpoint[1] - need)
end = (y, midpoint[1] + need)
2022-12-15 15:08:01 +00:00
2022-12-17 03:07:17 +00:00
return list(range(start[1], end[1] + 1))
2022-12-15 15:08:01 +00:00
2022-12-17 03:07:17 +00:00
def in_range(self, bY, bX):
d = cityblock(self.sY, self.sX, bY, bX)
2022-12-15 19:33:20 +00:00
if self.d < d:
return False
return True
2022-12-15 18:02:54 +00:00
def in_diamond(self):
2022-12-17 03:07:17 +00:00
sX, sY = self.sX, self.sY
2022-12-15 18:02:54 +00:00
up_lim = sY - self.distance
dn_lim = sY + self.distance
le_lim = sX - self.distance
ri_lim = sX + self.distance
u = (up_lim, sX)
d = (dn_lim, sX)
l = (sY, le_lim)
r = (sY, ri_lim)
2022-12-15 15:08:01 +00:00
2022-12-15 18:02:54 +00:00
infliction = 1
height = -1
2022-12-17 03:07:17 +00:00
for idx, x in enumerate(range(l[1], r[1] + 1)):
2022-12-15 18:02:54 +00:00
height += infliction
if (sY, x) == self.s:
infliction = -1
2022-12-17 03:07:17 +00:00
for y in range(sY - height, sY + height + 1):
yield (y, x)
2022-12-15 15:08:01 +00:00
2022-12-15 19:33:20 +00:00
def edges(self):
if self._edges:
return self._edges
2022-12-17 03:07:17 +00:00
sX, sY = self.sX, self.sY
2022-12-15 19:33:20 +00:00
up_lim = sY - self.distance
dn_lim = sY + self.distance
le_lim = sX - self.distance
ri_lim = sX + self.distance
u = (up_lim, sX)
d = (dn_lim, sX)
l = (sY, le_lim)
r = (sY, ri_lim)
infliction = 1
height = -1
edges = set()
# to left -1 and right + 1
2022-12-17 03:07:17 +00:00
for idx, x in enumerate(range(l[1], r[1] + 1)):
2022-12-15 19:33:20 +00:00
height += infliction
if (sY, x) == self.s:
infliction = -1
2022-12-17 03:07:17 +00:00
edges.add((sY - height, x))
edges.add((sY + height, x))
2022-12-15 19:33:20 +00:00
self._edges = edges
return self._edges
def border(self):
if self._border:
return self._border
2022-12-17 03:07:17 +00:00
sX, sY = self.sX, self.sY
2022-12-15 19:33:20 +00:00
up_lim = sY - self.distance
dn_lim = sY + self.distance
le_lim = sX - self.distance
ri_lim = sX + self.distance
u = (up_lim, sX)
d = (dn_lim, sX)
l = (sY, le_lim)
r = (sY, ri_lim)
infliction = 1
height = -1
border = set()
# to left -1 and right + 1
2022-12-17 03:07:17 +00:00
for idx, x in enumerate(range(l[1] - 1, r[1] + 2)):
2022-12-15 19:33:20 +00:00
height += infliction
if (sY, x) == self.s:
infliction = -1
2022-12-17 03:07:17 +00:00
border.add((sY - height, x))
border.add((sY + height, x))
2022-12-15 19:33:20 +00:00
self._border = border
return self._border
2022-12-15 06:17:19 +00:00
2022-12-15 18:02:54 +00:00
def part1(rows, sample=False):
2022-12-15 06:17:19 +00:00
sensors = []
sensor_points = []
beacon_points = []
2022-12-15 18:02:54 +00:00
ineligible_points = set()
2022-12-15 06:17:19 +00:00
xSet = set()
ySet = set()
for row in rows:
2022-12-17 03:07:17 +00:00
x, y, bx, by = scanf(
"Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row
)
2022-12-15 06:17:19 +00:00
xSet.add(x)
xSet.add(bx)
ySet.add(y)
ySet.add(by)
2022-12-17 03:07:17 +00:00
sensors.append(Sensor(sX=x, sY=y, bX=bx, bY=by, limit=(0, 0)))
minX, maxX = min(xSet), max(xSet)
minY, maxY = min(ySet), max(ySet)
limLo = min(minX, minY)
limHi = max(maxX, maxY)
2022-12-15 15:08:01 +00:00
2022-12-15 06:17:19 +00:00
for sensor in sensors:
2022-12-17 03:07:17 +00:00
sensor.limit = (limLo, limHi)
2022-12-15 18:02:54 +00:00
sensor_points.append(sensor.s)
2022-12-15 06:17:19 +00:00
beacon_points.append(sensor.b)
2022-12-15 18:02:54 +00:00
if sample:
for yx in sensor.in_diamond():
ineligible_points.add(yx)
2022-12-15 06:17:19 +00:00
2022-12-15 16:32:45 +00:00
CHECK_ROW = 2000000
2022-12-15 18:02:54 +00:00
if sample:
CHECK_ROW = 10
2022-12-15 15:08:01 +00:00
2022-12-15 06:17:19 +00:00
ineligible = set()
2022-12-15 15:08:01 +00:00
for s in sensors:
coll = s.on_line(CHECK_ROW)
ineligible.update(coll)
2022-12-17 03:07:17 +00:00
2022-12-15 16:32:45 +00:00
count_ignoring_current_beacons = 0
for i in ineligible:
if (CHECK_ROW, i) not in beacon_points:
count_ignoring_current_beacons += 1
2022-12-15 15:08:01 +00:00
2022-12-15 16:32:45 +00:00
print(count_ignoring_current_beacons, "with removing beacons, final answer")
2022-12-15 18:02:54 +00:00
if not sample:
return
2022-12-17 03:07:17 +00:00
mx = matrix.matrix_of_size(maxX + 1, maxY + 1)
2022-12-15 18:02:54 +00:00
for yx in ineligible_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 18:02:54 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "#"
for yx in beacon_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 18:02:54 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "B"
for yx in sensor_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 18:02:54 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "S"
2022-12-17 03:07:17 +00:00
print(matrix.ppmx(mx, pad=False, space=True))
tuning = lambda y, x: y + (4000000 * x)
2022-12-15 18:02:54 +00:00
2022-12-15 19:33:20 +00:00
def part2(rows, sample=False):
2022-12-15 18:02:54 +00:00
sensors = []
sensor_points = []
beacon_points = []
2022-12-15 19:33:20 +00:00
ineligible_points = set()
2022-12-15 18:02:54 +00:00
xSet = set()
ySet = set()
for row in rows:
2022-12-17 03:07:17 +00:00
x, y, bx, by = scanf(
"Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row
)
2022-12-15 18:02:54 +00:00
xSet.add(x)
xSet.add(bx)
ySet.add(y)
ySet.add(by)
2022-12-17 03:07:17 +00:00
sensors.append(Sensor(sX=x, sY=y, bX=bx, bY=by))
minX, maxX = min(xSet), max(xSet)
minY, maxY = min(ySet), max(ySet)
2022-12-15 15:08:01 +00:00
2022-12-15 18:02:54 +00:00
for sensor in sensors:
2022-12-15 19:33:20 +00:00
_ = sensor.edges()
_ = sensor.border()
sensor_points.append(sensor.s)
2022-12-15 18:02:54 +00:00
beacon_points.append(sensor.b)
2022-12-15 19:33:20 +00:00
if sample:
for yx in sensor.in_diamond():
ineligible_points.add(yx)
2022-12-15 06:17:19 +00:00
2022-12-15 19:33:20 +00:00
L = 4000000
if sample:
L = 20
2022-12-15 06:17:19 +00:00
2022-12-15 19:33:20 +00:00
borders = defaultdict(int)
2022-12-15 18:02:54 +00:00
for s in sensors:
2022-12-15 19:33:20 +00:00
for yx in s.border():
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 19:33:20 +00:00
if y > 0 and y <= L and x > 0 and x <= L:
borders[yx] += 1
TARGET = None
2022-12-17 03:07:17 +00:00
for (eY, eX) in borders.keys():
# print("checking:",(eY,ex))
2022-12-15 19:33:20 +00:00
away_from = []
for idx, s in enumerate(sensors):
2022-12-17 03:07:17 +00:00
d = s.distance_to(eY, eX)
2022-12-15 19:33:20 +00:00
if d > s.distance:
away_from.append(s.s)
if len(away_from) == len(sensors):
2022-12-17 03:07:17 +00:00
TARGET = (eY, eX)
print(TARGET, tuning(eY, eX))
2022-12-15 19:33:20 +00:00
break
if not sample:
return
2022-12-15 19:45:24 +00:00
# """ PRINT OUTPUT """
2022-12-17 03:07:17 +00:00
mx = matrix.matrix_of_size(maxX + 1, maxY + 1)
2022-12-15 19:33:20 +00:00
for yx in ineligible_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 19:33:20 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "#"
for yx in beacon_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 19:33:20 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "B"
for yx in sensor_points:
2022-12-17 03:07:17 +00:00
y, x = yx
2022-12-15 19:33:20 +00:00
if y >= 0 and x >= 0:
if y <= maxY and x <= maxX:
mx[y][x] = "S"
mx[TARGET[0]][TARGET[1]] = "!"
2022-12-17 03:07:17 +00:00
matrix.highlight(
mx,
blink_green=[
TARGET,
],
)
2022-12-15 19:33:20 +00:00
2022-12-15 06:17:19 +00:00
def main():
2022-12-15 18:02:54 +00:00
sample = False
if sys.argv[-1] == "--sample":
sample = True
2022-12-15 06:17:19 +00:00
rows = [row for row in shared.load_rows(15)]
with shared.elapsed_timer() as elapsed:
2022-12-15 18:02:54 +00:00
part1(rows, sample)
2022-12-15 06:17:19 +00:00
print("🕒", elapsed())
2022-12-15 19:33:20 +00:00
with shared.elapsed_timer() as elapsed:
part2(rows, sample)
print("🕒", elapsed())
2022-12-15 06:17:19 +00:00
if __name__ == "__main__":
main()