day16 still working:
This commit is contained in:
parent
139b6d7e12
commit
d7a1aac178
@ -3,16 +3,16 @@ import shared
|
|||||||
import itertools
|
import itertools
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
#@shared.profile
|
# @shared.profile
|
||||||
def part1(rows):
|
def part1(rows):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# @shared.profile
|
||||||
#@shared.profile
|
|
||||||
def part2(rows):
|
def part2(rows):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
rows = [row for row in shared.load_rows(15)]
|
rows = [row for row in shared.load_rows(15)]
|
||||||
with shared.elapsed_timer() as elapsed:
|
with shared.elapsed_timer() as elapsed:
|
||||||
|
@ -14,7 +14,7 @@ def part1(rows):
|
|||||||
# print(f"== Pair {idx+1} ==")
|
# print(f"== Pair {idx+1} ==")
|
||||||
passes = check_group(left, right)
|
passes = check_group(left, right)
|
||||||
if passes:
|
if passes:
|
||||||
indexes.append(idx+1)
|
indexes.append(idx + 1)
|
||||||
# if passes is True:
|
# if passes is True:
|
||||||
# print(f"{idx+1} {matrix.colors.GREEN}{passes}{matrix.colors.ENDC}")
|
# print(f"{idx+1} {matrix.colors.GREEN}{passes}{matrix.colors.ENDC}")
|
||||||
# elif passes is False:
|
# elif passes is False:
|
||||||
@ -62,42 +62,43 @@ def check_group(left, right):
|
|||||||
# If the left integer is higher than the right integer, the inputs are not in the right order.
|
# If the left integer is higher than the right integer, the inputs are not in the right order.
|
||||||
elif left > right:
|
elif left > right:
|
||||||
return False
|
return False
|
||||||
#Otherwise, the inputs are the same integer; continue checking the next part of the input.
|
# Otherwise, the inputs are the same integer; continue checking the next part of the input.
|
||||||
elif left == right:
|
elif left == right:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def part2(rows):
|
def part2(rows):
|
||||||
two, six = 0, 0
|
two, six = 0, 0
|
||||||
rows = [eval(r) for r in rows if r]
|
rows = [eval(r) for r in rows if r]
|
||||||
rows.append([[2]])
|
rows.append([[2]])
|
||||||
rows.append([[6]])
|
rows.append([[6]])
|
||||||
|
|
||||||
|
|
||||||
def cmp(x, y):
|
def cmp(x, y):
|
||||||
return {True: 1,False: -1,}[check_group(x, y)]
|
return {
|
||||||
|
True: 1,
|
||||||
|
False: -1,
|
||||||
|
}[check_group(x, y)]
|
||||||
|
|
||||||
idx = 0 # ew 1 based lists, jeeeze AoC
|
idx = 0 # ew 1 based lists, jeeeze AoC
|
||||||
for r in sorted(rows, key=functools.cmp_to_key(cmp), reverse=True):
|
for r in sorted(rows, key=functools.cmp_to_key(cmp), reverse=True):
|
||||||
idx += 1
|
idx += 1
|
||||||
if r == [[2]]:
|
if r == [[2]]:
|
||||||
two = idx
|
two = idx
|
||||||
if r == [[6]]:
|
if r == [[6]]:
|
||||||
six = idx
|
six = idx
|
||||||
break # will come 2nd logically, so we can break here to save time
|
break # will come 2nd logically, so we can break here to save time
|
||||||
|
|
||||||
print(two * six)
|
print(two * six)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
rows = shared.load_rows(13)
|
rows = shared.load_rows(13)
|
||||||
with shared.elapsed_timer() as elapsed:
|
with shared.elapsed_timer() as elapsed:
|
||||||
part1(rows)
|
part1(rows)
|
||||||
print("🕒", elapsed())
|
print("🕒", elapsed())
|
||||||
with shared.elapsed_timer() as elapsed:
|
with shared.elapsed_timer() as elapsed:
|
||||||
part2(rows)
|
part2(rows)
|
||||||
print("🕒", elapsed())
|
print("🕒", elapsed())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -4,7 +4,7 @@ import itertools
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
|
||||||
SAND_START = [0,500]
|
SAND_START = [0, 500]
|
||||||
|
|
||||||
|
|
||||||
def find_bounds(lines):
|
def find_bounds(lines):
|
||||||
@ -13,97 +13,110 @@ def find_bounds(lines):
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
x.extend(matrix.split_x_out(line))
|
x.extend(matrix.split_x_out(line))
|
||||||
y.extend(matrix.split_y_out(line))
|
y.extend(matrix.split_y_out(line))
|
||||||
return shared.minmax(y),shared.minmax(x)
|
return shared.minmax(y), shared.minmax(x)
|
||||||
|
|
||||||
|
|
||||||
def part1(rows):
|
def part1(rows):
|
||||||
def get_next(sand):
|
def get_next(sand):
|
||||||
next_y = sand[0]+1
|
next_y = sand[0] + 1
|
||||||
dl = sand[1]-1
|
dl = sand[1] - 1
|
||||||
d = sand[1]
|
d = sand[1]
|
||||||
dr = sand[1]+1
|
dr = sand[1] + 1
|
||||||
return next_y, (dl,d,dr)
|
return next_y, (dl, d, dr)
|
||||||
lines = [[list(reversed(list(map(int, point.split(","))))) for point in row.split(" -> ")] for row in rows]
|
|
||||||
(minY,maxY), (minX,maxX) = find_bounds(lines)
|
lines = [
|
||||||
mx = matrix.matrix_of_size(maxX+2,maxY+2)
|
[
|
||||||
|
list(reversed(list(map(int, point.split(",")))))
|
||||||
|
for point in row.split(" -> ")
|
||||||
|
]
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
(minY, maxY), (minX, maxX) = find_bounds(lines)
|
||||||
|
mx = matrix.matrix_of_size(maxX + 2, maxY + 2)
|
||||||
walls = []
|
walls = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
for x in range(len(line)-1):
|
for x in range(len(line) - 1):
|
||||||
coords = matrix.coords_between_points(line[x], line[x+1])
|
coords = matrix.coords_between_points(line[x], line[x + 1])
|
||||||
walls.extend(coords)
|
walls.extend(coords)
|
||||||
for y,x in walls:
|
for y, x in walls:
|
||||||
mx[y][x] = "#"
|
mx[y][x] = "#"
|
||||||
mx[SAND_START[0]][SAND_START[1]] = "+"
|
mx[SAND_START[0]][SAND_START[1]] = "+"
|
||||||
|
|
||||||
sands = []
|
sands = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for sand_grains in range(900): # produce Sand one unit at a time
|
for sand_grains in range(900): # produce Sand one unit at a time
|
||||||
last_grain = []
|
last_grain = []
|
||||||
sand = SAND_START[:]
|
sand = SAND_START[:]
|
||||||
#print("starting sand", sand_grains+1, sand)
|
# print("starting sand", sand_grains+1, sand)
|
||||||
while True:
|
while True:
|
||||||
last_grain.append(sand[:])
|
last_grain.append(sand[:])
|
||||||
next_y, (dl, d, dr) = get_next(sand)
|
next_y, (dl, d, dr) = get_next(sand)
|
||||||
if mx[next_y][d] == 0:
|
if mx[next_y][d] == 0:
|
||||||
#print(mx[next_y][d],"moving_down", end=' ')
|
# print(mx[next_y][d],"moving_down", end=' ')
|
||||||
#print("moving down")
|
# print("moving down")
|
||||||
sand[0] = next_y
|
sand[0] = next_y
|
||||||
elif mx[next_y][dl] == 0:
|
elif mx[next_y][dl] == 0:
|
||||||
#print(mx[next_y][dl], "moving left", end=' ')
|
# print(mx[next_y][dl], "moving left", end=' ')
|
||||||
#print("moving left")
|
# print("moving left")
|
||||||
sand[0] = next_y
|
sand[0] = next_y
|
||||||
sand[1] = dl
|
sand[1] = dl
|
||||||
elif mx[next_y][dr] == 0:
|
elif mx[next_y][dr] == 0:
|
||||||
#print(mx[next_y][dr], "moving right", end=' ')
|
# print(mx[next_y][dr], "moving right", end=' ')
|
||||||
#print("moving right")
|
# print("moving right")
|
||||||
sand[0] = next_y
|
sand[0] = next_y
|
||||||
sand[1] = dr
|
sand[1] = dr
|
||||||
else:
|
else:
|
||||||
mx[sand[0]][sand[1]] = "o"
|
mx[sand[0]][sand[1]] = "o"
|
||||||
#print(mx[next_y][dl], mx[next_y][d], mx[next_y][dr])
|
# print(mx[next_y][dl], mx[next_y][d], mx[next_y][dr])
|
||||||
#print("STOPPING", end=' ')
|
# print("STOPPING", end=' ')
|
||||||
break
|
break
|
||||||
#matrix.view_matrix(mx, 0,minX-1, maxY+1,maxX+2)
|
# matrix.view_matrix(mx, 0,minX-1, maxY+1,maxX+2)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
sands.append(last_grain)
|
sands.append(last_grain)
|
||||||
for y,x in sands[-1]:
|
for y, x in sands[-1]:
|
||||||
mx[y][x] = "~"
|
mx[y][x] = "~"
|
||||||
#matrix.view_matrix(mx, 0,minX-1, maxY+1,maxX+2)
|
# matrix.view_matrix(mx, 0,minX-1, maxY+1,maxX+2)
|
||||||
print("done", sand_grains)
|
print("done", sand_grains)
|
||||||
|
|
||||||
|
|
||||||
@shared.profile
|
@shared.profile
|
||||||
def part2(rows):
|
def part2(rows):
|
||||||
lines = [[list(reversed(list(map(int, point.split(","))))) for point in row.split(" -> ")] for row in rows]
|
lines = [
|
||||||
(minY,maxY), (minX,maxX) = find_bounds(lines)
|
[
|
||||||
|
list(reversed(list(map(int, point.split(",")))))
|
||||||
|
for point in row.split(" -> ")
|
||||||
|
]
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
(minY, maxY), (minX, maxX) = find_bounds(lines)
|
||||||
walls = {}
|
walls = {}
|
||||||
_between_points = matrix.coords_between_points
|
_between_points = matrix.coords_between_points
|
||||||
for line in lines:
|
for line in lines:
|
||||||
for x in range(len(line)-1):
|
for x in range(len(line) - 1):
|
||||||
coords = matrix.coords_between_points(line[x], line[x+1])
|
coords = matrix.coords_between_points(line[x], line[x + 1])
|
||||||
for c in coords:
|
for c in coords:
|
||||||
walls[c] = "#"
|
walls[c] = "#"
|
||||||
for y,x in walls:
|
for y, x in walls:
|
||||||
walls[(y,x)] = "#"# .add((y,x))
|
walls[(y, x)] = "#" # .add((y,x))
|
||||||
floor = maxY + 2
|
floor = maxY + 2
|
||||||
|
|
||||||
sand_grains = -1
|
sand_grains = -1
|
||||||
while True:
|
while True:
|
||||||
sand_grains += 1
|
sand_grains += 1
|
||||||
sand = [0,500]
|
sand = [0, 500]
|
||||||
|
|
||||||
if tuple(sand) in walls:
|
if tuple(sand) in walls:
|
||||||
print("done", sand_grains)
|
print("done", sand_grains)
|
||||||
break
|
break
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
next_y = sand[0]+1
|
next_y = sand[0] + 1
|
||||||
dl = sand[1]-1
|
dl = sand[1] - 1
|
||||||
dr = sand[1]+1
|
dr = sand[1] + 1
|
||||||
if next_y == floor:
|
if next_y == floor:
|
||||||
# hit the floor
|
# hit the floor
|
||||||
walls[tuple(sand)] = "#" # Draw stop
|
walls[tuple(sand)] = "#" # Draw stop
|
||||||
break
|
break
|
||||||
|
|
||||||
elif (next_y, sand[1]) not in walls:
|
elif (next_y, sand[1]) not in walls:
|
||||||
@ -120,6 +133,7 @@ def part2(rows):
|
|||||||
walls[tuple(sand)] = "#"
|
walls[tuple(sand)] = "#"
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
rows = [row for row in shared.load_rows(14)]
|
rows = [row for row in shared.load_rows(14)]
|
||||||
with shared.elapsed_timer() as elapsed:
|
with shared.elapsed_timer() as elapsed:
|
||||||
|
@ -8,8 +8,10 @@ from typing import Optional, List, Tuple
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
def cityblock(y1,x1, y2,x2):
|
|
||||||
return abs(y2-y1) + abs(x2-x1)
|
def cityblock(y1, x1, y2, x2):
|
||||||
|
return abs(y2 - y1) + abs(x2 - x1)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Sensor:
|
class Sensor:
|
||||||
@ -17,14 +19,16 @@ class Sensor:
|
|||||||
sY: int
|
sY: int
|
||||||
bX: int
|
bX: int
|
||||||
bY: int
|
bY: int
|
||||||
limit: Tuple[int,int] = (0,0)
|
limit: Tuple[int, int] = (0, 0)
|
||||||
_d: int = None
|
_d: int = None
|
||||||
_edges: List[Tuple[int,int]]= None
|
_edges: List[Tuple[int, int]] = None
|
||||||
_border: List[Tuple[int,int]]= None
|
_border: List[Tuple[int, int]] = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (f"Sensor(sX={self.sX}, sY={self.sY}, bX={self.bX},"
|
return (
|
||||||
f"bY={self.bY}, d={self._d}, edges={len(self._edges)}, borders={len(self._borders)})")
|
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)})"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def s(self):
|
def s(self):
|
||||||
@ -37,14 +41,14 @@ class Sensor:
|
|||||||
@property
|
@property
|
||||||
def distance(self):
|
def distance(self):
|
||||||
if self._d is None:
|
if self._d is None:
|
||||||
self._d = cityblock(self.sY,self.sX, self.bY, self.bX)
|
self._d = cityblock(self.sY, self.sX, self.bY, self.bX)
|
||||||
return self._d
|
return self._d
|
||||||
|
|
||||||
def distance_to(self, bY, bX):
|
def distance_to(self, bY, bX):
|
||||||
return cityblock(self.sY,self.sX, bY, bX)
|
return cityblock(self.sY, self.sX, bY, bX)
|
||||||
|
|
||||||
def on_line(self, y):
|
def on_line(self, y):
|
||||||
midpoint = (y,self.s[1])
|
midpoint = (y, self.s[1])
|
||||||
d = self.distance_to(*midpoint)
|
d = self.distance_to(*midpoint)
|
||||||
if d > self.distance:
|
if d > self.distance:
|
||||||
return []
|
return []
|
||||||
@ -54,17 +58,16 @@ class Sensor:
|
|||||||
start = (y, midpoint[1] - need)
|
start = (y, midpoint[1] - need)
|
||||||
end = (y, midpoint[1] + need)
|
end = (y, midpoint[1] + need)
|
||||||
|
|
||||||
return list(range(start[1],end[1]+1))
|
return list(range(start[1], end[1] + 1))
|
||||||
|
|
||||||
def in_range(self, bY,bX):
|
def in_range(self, bY, bX):
|
||||||
d = cityblock(self.sY,self.sX, bY, bX)
|
d = cityblock(self.sY, self.sX, bY, bX)
|
||||||
if self.d < d:
|
if self.d < d:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def in_diamond(self):
|
def in_diamond(self):
|
||||||
sX,sY = self.sX, self.sY
|
sX, sY = self.sX, self.sY
|
||||||
up_lim = sY - self.distance
|
up_lim = sY - self.distance
|
||||||
dn_lim = sY + self.distance
|
dn_lim = sY + self.distance
|
||||||
|
|
||||||
@ -78,17 +81,17 @@ class Sensor:
|
|||||||
|
|
||||||
infliction = 1
|
infliction = 1
|
||||||
height = -1
|
height = -1
|
||||||
for idx, x in enumerate(range(l[1],r[1]+1)):
|
for idx, x in enumerate(range(l[1], r[1] + 1)):
|
||||||
height += infliction
|
height += infliction
|
||||||
if (sY, x) == self.s:
|
if (sY, x) == self.s:
|
||||||
infliction = -1
|
infliction = -1
|
||||||
for y in range(sY-height, sY+height+1):
|
for y in range(sY - height, sY + height + 1):
|
||||||
yield (y,x)
|
yield (y, x)
|
||||||
|
|
||||||
def edges(self):
|
def edges(self):
|
||||||
if self._edges:
|
if self._edges:
|
||||||
return self._edges
|
return self._edges
|
||||||
sX,sY = self.sX, self.sY
|
sX, sY = self.sX, self.sY
|
||||||
up_lim = sY - self.distance
|
up_lim = sY - self.distance
|
||||||
dn_lim = sY + self.distance
|
dn_lim = sY + self.distance
|
||||||
|
|
||||||
@ -104,19 +107,19 @@ class Sensor:
|
|||||||
height = -1
|
height = -1
|
||||||
edges = set()
|
edges = set()
|
||||||
# to left -1 and right + 1
|
# to left -1 and right + 1
|
||||||
for idx, x in enumerate(range(l[1],r[1]+1)):
|
for idx, x in enumerate(range(l[1], r[1] + 1)):
|
||||||
height += infliction
|
height += infliction
|
||||||
if (sY, x) == self.s:
|
if (sY, x) == self.s:
|
||||||
infliction = -1
|
infliction = -1
|
||||||
edges.add((sY-height,x))
|
edges.add((sY - height, x))
|
||||||
edges.add((sY+height,x))
|
edges.add((sY + height, x))
|
||||||
self._edges = edges
|
self._edges = edges
|
||||||
return self._edges
|
return self._edges
|
||||||
|
|
||||||
def border(self):
|
def border(self):
|
||||||
if self._border:
|
if self._border:
|
||||||
return self._border
|
return self._border
|
||||||
sX,sY = self.sX, self.sY
|
sX, sY = self.sX, self.sY
|
||||||
up_lim = sY - self.distance
|
up_lim = sY - self.distance
|
||||||
dn_lim = sY + self.distance
|
dn_lim = sY + self.distance
|
||||||
|
|
||||||
@ -132,12 +135,12 @@ class Sensor:
|
|||||||
height = -1
|
height = -1
|
||||||
border = set()
|
border = set()
|
||||||
# to left -1 and right + 1
|
# to left -1 and right + 1
|
||||||
for idx, x in enumerate(range(l[1]-1,r[1]+2)):
|
for idx, x in enumerate(range(l[1] - 1, r[1] + 2)):
|
||||||
height += infliction
|
height += infliction
|
||||||
if (sY, x) == self.s:
|
if (sY, x) == self.s:
|
||||||
infliction = -1
|
infliction = -1
|
||||||
border.add((sY-height,x))
|
border.add((sY - height, x))
|
||||||
border.add((sY+height,x))
|
border.add((sY + height, x))
|
||||||
self._border = border
|
self._border = border
|
||||||
return self._border
|
return self._border
|
||||||
|
|
||||||
@ -150,19 +153,21 @@ def part1(rows, sample=False):
|
|||||||
xSet = set()
|
xSet = set()
|
||||||
ySet = set()
|
ySet = set()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
x,y,bx,by = scanf("Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row)
|
x, y, bx, by = scanf(
|
||||||
|
"Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row
|
||||||
|
)
|
||||||
xSet.add(x)
|
xSet.add(x)
|
||||||
xSet.add(bx)
|
xSet.add(bx)
|
||||||
ySet.add(y)
|
ySet.add(y)
|
||||||
ySet.add(by)
|
ySet.add(by)
|
||||||
sensors.append(Sensor(sX=x,sY=y,bX=bx,bY=by,limit=(0,0)))
|
sensors.append(Sensor(sX=x, sY=y, bX=bx, bY=by, limit=(0, 0)))
|
||||||
minX, maxX = min(xSet),max(xSet)
|
minX, maxX = min(xSet), max(xSet)
|
||||||
minY, maxY = min(ySet),max(ySet)
|
minY, maxY = min(ySet), max(ySet)
|
||||||
limLo = min(minX,minY)
|
limLo = min(minX, minY)
|
||||||
limHi = max(maxX,maxY)
|
limHi = max(maxX, maxY)
|
||||||
|
|
||||||
for sensor in sensors:
|
for sensor in sensors:
|
||||||
sensor.limit = (limLo,limHi)
|
sensor.limit = (limLo, limHi)
|
||||||
sensor_points.append(sensor.s)
|
sensor_points.append(sensor.s)
|
||||||
beacon_points.append(sensor.b)
|
beacon_points.append(sensor.b)
|
||||||
if sample:
|
if sample:
|
||||||
@ -187,25 +192,27 @@ def part1(rows, sample=False):
|
|||||||
if not sample:
|
if not sample:
|
||||||
return
|
return
|
||||||
|
|
||||||
mx = matrix.matrix_of_size(maxX+1, maxY+1)
|
mx = matrix.matrix_of_size(maxX + 1, maxY + 1)
|
||||||
for yx in ineligible_points:
|
for yx in ineligible_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "#"
|
mx[y][x] = "#"
|
||||||
for yx in beacon_points:
|
for yx in beacon_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "B"
|
mx[y][x] = "B"
|
||||||
for yx in sensor_points:
|
for yx in sensor_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "S"
|
mx[y][x] = "S"
|
||||||
print(matrix.ppmx(mx, pad=False,space=True))
|
print(matrix.ppmx(mx, pad=False, space=True))
|
||||||
|
|
||||||
|
|
||||||
|
tuning = lambda y, x: y + (4000000 * x)
|
||||||
|
|
||||||
tuning = lambda y,x: y + (4000000 * x)
|
|
||||||
|
|
||||||
def part2(rows, sample=False):
|
def part2(rows, sample=False):
|
||||||
sensors = []
|
sensors = []
|
||||||
@ -215,14 +222,16 @@ def part2(rows, sample=False):
|
|||||||
xSet = set()
|
xSet = set()
|
||||||
ySet = set()
|
ySet = set()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
x,y,bx,by = scanf("Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row)
|
x, y, bx, by = scanf(
|
||||||
|
"Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", row
|
||||||
|
)
|
||||||
xSet.add(x)
|
xSet.add(x)
|
||||||
xSet.add(bx)
|
xSet.add(bx)
|
||||||
ySet.add(y)
|
ySet.add(y)
|
||||||
ySet.add(by)
|
ySet.add(by)
|
||||||
sensors.append(Sensor(sX=x,sY=y,bX=bx,bY=by))
|
sensors.append(Sensor(sX=x, sY=y, bX=bx, bY=by))
|
||||||
minX, maxX = min(xSet),max(xSet)
|
minX, maxX = min(xSet), max(xSet)
|
||||||
minY, maxY = min(ySet),max(ySet)
|
minY, maxY = min(ySet), max(ySet)
|
||||||
|
|
||||||
for sensor in sensors:
|
for sensor in sensors:
|
||||||
_ = sensor.edges()
|
_ = sensor.edges()
|
||||||
@ -240,21 +249,21 @@ def part2(rows, sample=False):
|
|||||||
borders = defaultdict(int)
|
borders = defaultdict(int)
|
||||||
for s in sensors:
|
for s in sensors:
|
||||||
for yx in s.border():
|
for yx in s.border():
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y > 0 and y <= L and x > 0 and x <= L:
|
if y > 0 and y <= L and x > 0 and x <= L:
|
||||||
borders[yx] += 1
|
borders[yx] += 1
|
||||||
|
|
||||||
TARGET = None
|
TARGET = None
|
||||||
for (eY,eX) in borders.keys():
|
for (eY, eX) in borders.keys():
|
||||||
#print("checking:",(eY,ex))
|
# print("checking:",(eY,ex))
|
||||||
away_from = []
|
away_from = []
|
||||||
for idx, s in enumerate(sensors):
|
for idx, s in enumerate(sensors):
|
||||||
d = s.distance_to(eY,eX)
|
d = s.distance_to(eY, eX)
|
||||||
if d > s.distance:
|
if d > s.distance:
|
||||||
away_from.append(s.s)
|
away_from.append(s.s)
|
||||||
if len(away_from) == len(sensors):
|
if len(away_from) == len(sensors):
|
||||||
TARGET = (eY,eX)
|
TARGET = (eY, eX)
|
||||||
print(TARGET, tuning(eY,eX))
|
print(TARGET, tuning(eY, eX))
|
||||||
break
|
break
|
||||||
|
|
||||||
if not sample:
|
if not sample:
|
||||||
@ -262,30 +271,32 @@ def part2(rows, sample=False):
|
|||||||
|
|
||||||
# """ PRINT OUTPUT """
|
# """ PRINT OUTPUT """
|
||||||
|
|
||||||
mx = matrix.matrix_of_size(maxX+1, maxY+1)
|
mx = matrix.matrix_of_size(maxX + 1, maxY + 1)
|
||||||
for yx in ineligible_points:
|
for yx in ineligible_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "#"
|
mx[y][x] = "#"
|
||||||
|
|
||||||
for yx in beacon_points:
|
for yx in beacon_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "B"
|
mx[y][x] = "B"
|
||||||
|
|
||||||
for yx in sensor_points:
|
for yx in sensor_points:
|
||||||
y,x = yx
|
y, x = yx
|
||||||
if y >= 0 and x >= 0:
|
if y >= 0 and x >= 0:
|
||||||
if y <= maxY and x <= maxX:
|
if y <= maxY and x <= maxX:
|
||||||
mx[y][x] = "S"
|
mx[y][x] = "S"
|
||||||
|
|
||||||
|
|
||||||
mx[TARGET[0]][TARGET[1]] = "!"
|
mx[TARGET[0]][TARGET[1]] = "!"
|
||||||
matrix.highlight(mx, blink_green=[TARGET,])
|
matrix.highlight(
|
||||||
|
mx,
|
||||||
|
blink_green=[
|
||||||
|
TARGET,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -3,9 +3,12 @@ import matrix
|
|||||||
import shared
|
import shared
|
||||||
import scanf
|
import scanf
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from collections import defaultdict
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
from IPython.display import Image, display
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Valve:
|
class Valve:
|
||||||
@ -13,7 +16,7 @@ class Valve:
|
|||||||
rate: int
|
rate: int
|
||||||
tunnels: List[str]
|
tunnels: List[str]
|
||||||
opened_at: int = -1
|
opened_at: int = -1
|
||||||
potential: Dict[str,int] = None
|
potential: Dict[str, int] = None
|
||||||
|
|
||||||
def set_potential(self, valves):
|
def set_potential(self, valves):
|
||||||
self.potential = {}
|
self.potential = {}
|
||||||
@ -23,6 +26,7 @@ class Valve:
|
|||||||
def highest_potential(self):
|
def highest_potential(self):
|
||||||
return max(self.potential, key=self.potential.get)
|
return max(self.potential, key=self.potential.get)
|
||||||
|
|
||||||
|
|
||||||
def parse(rows):
|
def parse(rows):
|
||||||
valves = {}
|
valves = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
@ -30,15 +34,15 @@ def parse(rows):
|
|||||||
right = right.replace("s ", "").lstrip()
|
right = right.replace("s ", "").lstrip()
|
||||||
valve, rate = scanf.scanf("Valve %s has flow rate=%d; %*s %*s to", left)
|
valve, rate = scanf.scanf("Valve %s has flow rate=%d; %*s %*s to", left)
|
||||||
tunnels = right.split(", ")
|
tunnels = right.split(", ")
|
||||||
valves[valve] = Valve(label=valve,rate=rate,tunnels=tunnels)
|
valves[valve] = Valve(label=valve, rate=rate, tunnels=tunnels)
|
||||||
|
|
||||||
for _,v in valves.items():
|
for _, v in valves.items():
|
||||||
v.set_potential(valves)
|
v.set_potential(valves)
|
||||||
return valves
|
return valves
|
||||||
|
|
||||||
|
|
||||||
def part1(rows, sample=False):
|
def part1(rows, sample=False):
|
||||||
p1 = Part1(rows,sample, 30)
|
p1 = Part1(rows, sample, 30)
|
||||||
p1.run()
|
p1.run()
|
||||||
|
|
||||||
|
|
||||||
@ -47,60 +51,91 @@ class Part1:
|
|||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.sample = sample
|
self.sample = sample
|
||||||
self.valves = parse(rows)
|
self.valves = parse(rows)
|
||||||
|
self.nonzero = {v.label: v for _, v in self.valves.items() if v.rate > 0}
|
||||||
self.cur = self.valves["AA"]
|
self.cur = self.valves["AA"]
|
||||||
self.tick = 1
|
self.tick = 1
|
||||||
self.minutes = minutes
|
self.minutes = minutes
|
||||||
|
self.g = nx.DiGraph()
|
||||||
|
self.path_distances = defaultdict(dict)
|
||||||
|
self.set_up_graph()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
pdot = nx.drawing.nx_pydot.to_pydot(self.g)
|
||||||
|
pdot.write_png("15.png")
|
||||||
|
|
||||||
|
def set_up_graph(self):
|
||||||
|
for lbl, v in self.valves.items():
|
||||||
|
for t in v.tunnels:
|
||||||
|
# self.g.add_edge(lbl, t, {'weight':self.valves[t].rate})
|
||||||
|
self.g.add_edge(lbl, t, weight=self.valves[t].rate)
|
||||||
|
all_keys = self.valves.keys()
|
||||||
|
for lbl, _ in self.valves.items():
|
||||||
|
for other in all_keys:
|
||||||
|
if other == lbl:
|
||||||
|
continue
|
||||||
|
self.path_distances[lbl][other] = min([len(x) for x in nx.shortest_simple_paths(self.g, lbl, other)])
|
||||||
|
|
||||||
def do_tick(self, minute):
|
def do_tick(self, minute):
|
||||||
pressure = 0
|
pressure = 0
|
||||||
opened = []
|
opened = []
|
||||||
for _, valve in self.valves.items():
|
for _, valve in self.valves.items():
|
||||||
if valve.opened_at > 0:
|
if valve.opened_at > 0:
|
||||||
continue
|
pressure += valve.rate
|
||||||
pressure += valve.rate
|
opened.append(valve.label)
|
||||||
print(f"== Minute {minute} == [ {self.cur.label} ]")
|
print(f"== Min {minute}:: Valves {', '.join(opened)} are open, releasing {pressure} pressure")
|
||||||
print(f"\tValves {', '.join(opened)} are open, releasing {pressure} pressure")
|
|
||||||
|
|
||||||
|
|
||||||
def open_or_move(self):
|
|
||||||
#print(self.cur)
|
|
||||||
hi = self.cur.highest_potential()
|
|
||||||
potential_elsewhere = self.cur.potential
|
|
||||||
pe = potential_elsewhere[hi]
|
|
||||||
if pe > self.cur.rate:
|
|
||||||
#print(f"should move to {hi}")
|
|
||||||
self.move(hi)
|
|
||||||
elif self.cur.rate > pe:
|
|
||||||
print(f"should open {self.cur.label}")
|
|
||||||
self.open()
|
|
||||||
|
|
||||||
def move(self, where):
|
|
||||||
self.tick += 1
|
|
||||||
self.cur = self.valves[where]
|
|
||||||
print("\tMove to valve", where)
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
if self.cur.opened_at >= 0 :
|
|
||||||
raise Exception("tried to open valve already opened")
|
|
||||||
self.tick += 1
|
|
||||||
self.valves[self.cur.label].opened_at = self.tick
|
|
||||||
self.cur = self.valves[self.cur.label]
|
|
||||||
print(self.cur)
|
|
||||||
|
|
||||||
def calculate_total_flow(self):
|
def calculate_total_flow(self):
|
||||||
total = 0
|
total = 0
|
||||||
for label, valve in self.valves.items():
|
for label, valve in self.valves.items():
|
||||||
if valve.is_open:
|
if valve.opened_at > 0:
|
||||||
total += valve.rate * (30 - valve.opened_at)
|
total += valve.rate * (30 - valve.opened_at)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for minute in range(1, self.minutes+1):
|
# Construct the graph with vertices & edges from the input
|
||||||
self.do_tick(minute)
|
# Call a function to compute the distances between every pair of vertices
|
||||||
self.open_or_move()
|
# Create a closed set containing all the valves with non-zero rates
|
||||||
|
# At each step, iterate over the remaining set of closed, non-zero valves
|
||||||
|
# - Subtract the distance from remaining minutes
|
||||||
|
# - Calculate the flow (rate * remaining minutes)
|
||||||
|
# - Remove the recently opened valve from the closed set (functionally), so the deeper levels won't consider it
|
||||||
|
|
||||||
print("total flow:")
|
def priority(remaining):
|
||||||
print(self.calculate_total_flow())
|
_pris = []
|
||||||
|
for _,n in self.nonzero.items():
|
||||||
|
# (time_remaining - distance_to_valve - 1) * flow rate
|
||||||
|
pri = (remaining - self.path_distances[self.cur.label][n.label] - 1) * n.rate
|
||||||
|
_pris.append((n.label, pri))
|
||||||
|
_pris = list(sorted(_pris, key=lambda x: x[1], reverse=True))
|
||||||
|
return _pris
|
||||||
|
|
||||||
|
|
||||||
|
remaining = self.minutes
|
||||||
|
open_order = []
|
||||||
|
while len(self.nonzero):
|
||||||
|
if remaining <= 0:
|
||||||
|
print("ran out of time")
|
||||||
|
break
|
||||||
|
self.do_tick(31-remaining)
|
||||||
|
pris = priority(remaining)
|
||||||
|
print(pris)
|
||||||
|
_pri, _ = pris.pop(0)
|
||||||
|
n = self.nonzero[_pri]
|
||||||
|
del self.nonzero[_pri]
|
||||||
|
open_order.append(n.label)
|
||||||
|
print("\tMoving to", n.label)
|
||||||
|
print("\tOpening ", n.label)
|
||||||
|
distance = self.path_distances[self.cur.label][n.label]
|
||||||
|
self.cur = n
|
||||||
|
self.cur.opened_at = self.minutes - (remaining + 1)
|
||||||
|
remaining -= distance # Move
|
||||||
|
remaining -= 1 # open
|
||||||
|
|
||||||
|
|
||||||
|
print(remaining)
|
||||||
|
print(open_order)
|
||||||
|
print("sample: 1651")
|
||||||
|
print("total flow:", self.calculate_total_flow())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -114,7 +149,7 @@ def main():
|
|||||||
part1(rows, sample)
|
part1(rows, sample)
|
||||||
print("🕒", elapsed())
|
print("🕒", elapsed())
|
||||||
|
|
||||||
#with shared.elapsed_timer() as elapsed:
|
# with shared.elapsed_timer() as elapsed:
|
||||||
# part2(rows, sample)
|
# part2(rows, sample)
|
||||||
# print("🕒", elapsed())
|
# print("🕒", elapsed())
|
||||||
|
|
||||||
|
@ -8,11 +8,13 @@ split_word_to_chr_list = lambda y: [w for w in y]
|
|||||||
split_word_to_int_list = lambda y: [int(w) for w in y]
|
split_word_to_int_list = lambda y: [int(w) for w in y]
|
||||||
split_line_to_int_list = lambda y: [int(w) for w in y.split(" ") if w]
|
split_line_to_int_list = lambda y: [int(w) for w in y.split(" ") if w]
|
||||||
|
|
||||||
|
|
||||||
def split_x_out(l):
|
def split_x_out(l):
|
||||||
return [x for _,x in l]
|
return [x for _, x in l]
|
||||||
|
|
||||||
|
|
||||||
def split_y_out(l):
|
def split_y_out(l):
|
||||||
return [y for y,_ in l]
|
return [y for y, _ in l]
|
||||||
|
|
||||||
|
|
||||||
class colors:
|
class colors:
|
||||||
@ -154,7 +156,9 @@ def get_neighbor_coords(matrix, c, r, diagonals=True):
|
|||||||
return neighbors
|
return neighbors
|
||||||
|
|
||||||
|
|
||||||
def line_of_sight_coords(matrix, row, col, distance=None) -> Dict[str, List[Tuple[int, int]]]:
|
def line_of_sight_coords(
|
||||||
|
matrix, row, col, distance=None
|
||||||
|
) -> Dict[str, List[Tuple[int, int]]]:
|
||||||
"""
|
"""
|
||||||
Takes a matrix, a row, and a column
|
Takes a matrix, a row, and a column
|
||||||
calculates the coordinates to the edge for all four cardinal directions
|
calculates the coordinates to the edge for all four cardinal directions
|
||||||
@ -168,8 +172,14 @@ def line_of_sight_coords(matrix, row, col, distance=None) -> Dict[str, List[Tupl
|
|||||||
row_ids = list(range(0, width))
|
row_ids = list(range(0, width))
|
||||||
|
|
||||||
if distance:
|
if distance:
|
||||||
up_ids, down_ids = list(reversed(col_ids[:col])), col_ids[col + 1 :col+distance+1]
|
up_ids, down_ids = (
|
||||||
left_ids, right_ids = list(reversed(row_ids[:row])), row_ids[row + 1 :row+distance+1]
|
list(reversed(col_ids[:col])),
|
||||||
|
col_ids[col + 1 : col + distance + 1],
|
||||||
|
)
|
||||||
|
left_ids, right_ids = (
|
||||||
|
list(reversed(row_ids[:row])),
|
||||||
|
row_ids[row + 1 : row + distance + 1],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
up_ids, down_ids = list(reversed(col_ids[:col])), col_ids[col + 1 :]
|
up_ids, down_ids = list(reversed(col_ids[:col])), col_ids[col + 1 :]
|
||||||
left_ids, right_ids = list(reversed(row_ids[:row])), row_ids[row + 1 :]
|
left_ids, right_ids = list(reversed(row_ids[:row])), row_ids[row + 1 :]
|
||||||
@ -200,8 +210,8 @@ def line_of_sight(mx, row, col, distance=None):
|
|||||||
|
|
||||||
|
|
||||||
def coords_between_points(point1, point2):
|
def coords_between_points(point1, point2):
|
||||||
y1,x1 = point1
|
y1, x1 = point1
|
||||||
y2,x2 = point2
|
y2, x2 = point2
|
||||||
|
|
||||||
coords = []
|
coords = []
|
||||||
x = 0
|
x = 0
|
||||||
@ -209,25 +219,23 @@ def coords_between_points(point1, point2):
|
|||||||
|
|
||||||
if x2 < x1:
|
if x2 < x1:
|
||||||
y = point1[0]
|
y = point1[0]
|
||||||
for _x in range(x2, x1+1):
|
for _x in range(x2, x1 + 1):
|
||||||
coords.append((y,_x))
|
coords.append((y, _x))
|
||||||
elif x1 < x2:
|
elif x1 < x2:
|
||||||
y = point1[0]
|
y = point1[0]
|
||||||
for _x in range(x1, x2+1):
|
for _x in range(x1, x2 + 1):
|
||||||
coords.append((y,_x))
|
coords.append((y, _x))
|
||||||
elif y2 < y1:
|
elif y2 < y1:
|
||||||
x = point1[1]
|
x = point1[1]
|
||||||
for _y in range(y2, y1+1):
|
for _y in range(y2, y1 + 1):
|
||||||
coords.append((_y,x))
|
coords.append((_y, x))
|
||||||
elif y1 < y2:
|
elif y1 < y2:
|
||||||
x = point1[1]
|
x = point1[1]
|
||||||
for _y in range(y1, y2+1):
|
for _y in range(y1, y2 + 1):
|
||||||
coords.append((_y,x))
|
coords.append((_y, x))
|
||||||
return coords
|
return coords
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_size(matrix):
|
def get_size(matrix):
|
||||||
height = len(matrix)
|
height = len(matrix)
|
||||||
width = len(matrix[0])
|
width = len(matrix[0])
|
||||||
@ -303,14 +311,13 @@ def ppmx(*matrices, pad=True, space=True, zero="."):
|
|||||||
out.append("".join([f(x) for x in c]))
|
out.append("".join([f(x) for x in c]))
|
||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
|
|
||||||
def view_matrix(matrix, y1,x1, y2,x2):
|
|
||||||
lines = ppmx(matrix, pad=0,space=0).split("\n")
|
def view_matrix(matrix, y1, x1, y2, x2):
|
||||||
for line in lines[y1:y2+1]:
|
lines = ppmx(matrix, pad=0, space=0).split("\n")
|
||||||
|
for line in lines[y1 : y2 + 1]:
|
||||||
print(line[x1:x2])
|
print(line[x1:x2])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def highlight(matrix, red=[], green=[], blue=[], blink_green=[]):
|
def highlight(matrix, red=[], green=[], blue=[], blink_green=[]):
|
||||||
"""
|
"""
|
||||||
print a matrix of anything, Falsy values turns to `.` for clarity
|
print a matrix of anything, Falsy values turns to `.` for clarity
|
||||||
@ -335,5 +342,3 @@ def highlight(matrix, red=[], green=[], blue=[], blink_green=[]):
|
|||||||
new = f"{colors.BLINK}{colors.GREEN}{mx[y][x]}{colors.ENDC}"
|
new = f"{colors.BLINK}{colors.GREEN}{mx[y][x]}{colors.ENDC}"
|
||||||
mx[y][x] = new
|
mx[y][x] = new
|
||||||
print(ppmx(mx, pad=False, space=True, zero="0"))
|
print(ppmx(mx, pad=False, space=True, zero="0"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ import cProfile
|
|||||||
import functools
|
import functools
|
||||||
import pstats
|
import pstats
|
||||||
|
|
||||||
def profile(func):
|
|
||||||
|
|
||||||
|
def profile(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
||||||
profiler = cProfile.Profile()
|
profiler = cProfile.Profile()
|
||||||
@ -15,18 +15,20 @@ def profile(func):
|
|||||||
retval = func(*args, **kwargs)
|
retval = func(*args, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
profiler.disable()
|
profiler.disable()
|
||||||
with open('profile.out', 'w') as profile_file:
|
with open("profile.out", "w") as profile_file:
|
||||||
stats = pstats.Stats(profiler, stream=profile_file)
|
stats = pstats.Stats(profiler, stream=profile_file)
|
||||||
stats.print_stats()
|
stats.print_stats()
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
||||||
spl = lambda y: [int(w) for w in y]
|
spl = lambda y: [int(w) for w in y]
|
||||||
|
|
||||||
|
|
||||||
def minmax(l):
|
def minmax(l):
|
||||||
return min(l),max(l)
|
return min(l), max(l)
|
||||||
|
|
||||||
|
|
||||||
def load_rows(day):
|
def load_rows(day):
|
||||||
return [row for row in load(day)]
|
return [row for row in load(day)]
|
||||||
|
Loading…
Reference in New Issue
Block a user