day16 still working:

This commit is contained in:
Tyrel Souza 2022-12-16 22:07:17 -05:00
parent 139b6d7e12
commit d7a1aac178
7 changed files with 247 additions and 179 deletions

View File

@ -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:

View File

@ -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:
@ -26,7 +26,7 @@ def part1(rows):
def check_group(left, right): def check_group(left, right):
if isinstance(left, list) and isinstance(right, list): if isinstance(left, list) and isinstance(right, list):
# if both items are lists, loop over both # if both items are lists, loop over both
for idx in range(min(len(left), len(right))): for idx in range(min(len(left), len(right))):
check = check_group(left[idx], right[idx]) check = check_group(left[idx], right[idx])
# bubble up the int comparisons, or list length comparisons # bubble up the int comparisons, or list length comparisons
@ -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__":

View File

@ -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:

View File

@ -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,15 +41,15 @@ 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:
@ -177,7 +182,7 @@ def part1(rows, sample=False):
for s in sensors: for s in sensors:
coll = s.on_line(CHECK_ROW) coll = s.on_line(CHECK_ROW)
ineligible.update(coll) ineligible.update(coll)
count_ignoring_current_beacons = 0 count_ignoring_current_beacons = 0
for i in ineligible: for i in ineligible:
if (CHECK_ROW, i) not in beacon_points: if (CHECK_ROW, i) not in beacon_points:
@ -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():

View File

@ -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,62 +51,93 @@ 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())
def main(): def main():
sample = False sample = False
@ -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())

View File

@ -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,23 +219,21 @@ 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):
@ -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"))

View File

@ -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)]