Compare commits

...

7 Commits

Author SHA1 Message Date
magical 88040394db day 18 ivy solution 2022-12-18 17:26:40 -08:00
magical 772dda0bbc day 18 python fix a flood fill bug
not sure how this ever worked
2022-12-17 22:37:20 -08:00
magical 4b5b25e56b day 18 python cleanup 2022-12-17 22:07:34 -08:00
magical 71958605b6 day 18 python solution 2022-12-17 21:41:26 -08:00
magical 910da239c0 new A* optimization: worst-case pruning
i'm not sure if this has been described in the literature exactly but it
is similar to existing branch-and-bound techniques.

it doesn't speed up the search directly (the number of visited nodes is
the same) but it does cut the queue length down by an order of magnitude
and shave a couple seconds off of day 16, presumably due to reduced memory
pressure or something.
2022-12-17 20:15:25 -08:00
magical e9ada04aa9 lib/astar: report final queue length 2022-12-17 19:29:26 -08:00
magical e1fb37d229 lib/astar: handle inconsistent heuristics better
before pruning a node we've already visited, we need to check if it has
a better cost.
2022-12-17 19:27:28 -08:00
7 changed files with 3016 additions and 6 deletions

View File

@ -152,6 +152,10 @@ def solve():
m -= 1
return c
def worst2(node):
_, _, min1, min2, closed = node
return min(min1,min2) * pressure(closed)
def is_goal2(node):
_, _, min1, min2, closed = node
return min1 == 0 and min2 == 0 or closed == all_open
@ -209,7 +213,7 @@ def solve():
minutes = 26
start2 = (AA, AA, minutes, minutes, all_closed)
info.clear()
c, _, path = astar.search(start2, is_goal2, neighbors2, heuristic2)
c, _, path = astar.search(start2, is_goal2, neighbors2, heuristic2, worst=worst2)
print(c)
print(max_pressure*minutes - c)

2866
day18/input 100644

File diff suppressed because it is too large Load Diff

1
day18/input.ivy 100644

File diff suppressed because one or more lines are too long

13
day18/sample1.in 100644
View File

@ -0,0 +1,13 @@
2,2,2
1,2,2
3,2,2
2,1,2
2,3,2
2,2,1
2,2,3
2,2,4
2,2,6
1,2,5
3,2,5
2,1,5
2,3,5

52
day18/sol.ivy 100644
View File

@ -0,0 +1,52 @@
) get "input.ivy"
sample = 13 3 rho 2 2 2 1 2 2 3 2 2 2 1 2 2 3 2 2 2 1 2 2 3 2 2 4 2 2 6 1 2 5 3 2 5 2 1 5 2 3 5
#input = sample
) origin 0
op makeGrid coords =
lo = min/ transp coords
hi = max/ transp coords
coords = 1 1 1 + coords - lo
dim = 2 + (1 + hi - lo)
g = ,dim rho 0
g[dim decode transp coords] = 1
dim rho g
op makeBorder dim =
g = (dim-2) rho 0
g = 1,(2 0 1 transp g),1
g = 1,(2 0 1 transp g),1
g = 1,(2 0 1 transp g),1
g
op n twist a = 1 0 2 transp n flip (1 0 2 transp a)
op grow a = (1 rot a) or (-1 rot a) or (1 flip a) or (-1 flip a) or (1 twist a) or (-1 twist a)
op sides a = (1 rot a) + (-1 rot a) + (1 flip a) + (-1 flip a) + (1 twist a) + (-1 twist a)
lava = makeGrid input
"npoints = "; +/ ,lava
#lava
#border = makeBorder rho lava
#or/, lava and border # should == 0
op a flood walls =
x = grow a
b = (a or x) and not walls
and/, a == b: a
b flood walls
op solve2 lava =
water = (makeBorder rho lava) flood lava
+/, lava * sides water
op solve1 lava =
air = not lava
+/, lava * sides air
"part 1 ="; solve1 lava
"part 2 ="; solve2 lava

62
day18/sol.py 100644
View File

@ -0,0 +1,62 @@
import numpy
N = 23
def solve():
lava = numpy.zeros((N,N,N), dtype=numpy.int32)
for line in open("input"):
x,y,z = map(int, line.strip().split(","))
lava[x,y,z] = 1
lava = numpy.pad(lava, (1,1), constant_values=0)
# part 1
area = count_touching(lava, lava^1)
print(area)
# part 2
border = numpy.zeros((N,N,N), dtype=numpy.int32)
border = numpy.pad(border, (1,1), constant_values=1)
water = flood(lava, border)
#print(water)
area = count_touching(lava, water)
print(area)
def count_touching(A, B):
"""counts the number of places a cell in A touches a cell in B
(assumes a 1-cell padding around the edge which is not counted)"""
n = 0
for x in range(1,N+1):
for y in range(1,N+1):
for z in range(1,N+1):
if A[x,y,z]:
n += B[x-1,y,z]
n += B[x+1,y,z]
n += B[x,y-1,z]
n += B[x,y+1,z]
n += B[x,y,z-1]
n += B[x,y,z+1]
return n
def flood(lava, border):
def roll(a, axis, amount):
return numpy.roll(a, amount, axis=axis)
a = border
while True:
x = roll(a, 0, -1)
x |= roll(a, 0, +1)
x |= roll(a, 1, -1)
x |= roll(a, 1, +1)
x |= roll(a, 2, -1)
x |= roll(a, 2, +1)
x &= ~lava
b = a | x
if (a == b).all():
return a
a = b
solve()

View File

@ -1,6 +1,6 @@
from heapq import heappush, heappop
def search(start, is_goal, neighbors, heuristic=None):
def search(start, is_goal, neighbors, heuristic=None, worst=None):
if heuristic == None:
def heuristic(x):
return 0
@ -16,6 +16,8 @@ def search(start, is_goal, neighbors, heuristic=None):
i += 1
heappush(q, (heuristic(s), i, s, None))
done = {}
if worst:
best_worst = min(worst(s) for s in start)
while q:
z, _, this, prev = heappop(q)
if this in done:
@ -27,6 +29,7 @@ def search(start, is_goal, neighbors, heuristic=None):
if is_goal(this):
print("astar: visited", len(done), "nodes")
print("astar: pending", len(q), "nodes")
# reconsruct the path
n = this
path = []
@ -37,11 +40,20 @@ def search(start, is_goal, neighbors, heuristic=None):
return cost_so_far, this, path
for c, n in neighbors(this):
if n not in done:
# calculate the "reduced cost"
c = cost_so_far + c + heuristic(n)
c = cost_so_far + c
if n not in done or c < done[n][0]:
h = heuristic(n)
if worst:
if c+h > best_worst:
# if the best possible cost for this node
# is worse than the lowest worst-case cost we've seen
# then don't even bother exploring it
continue
w = worst(n)
if c+w < best_worst:
best_worst = c+w
i += 1
heappush(q, (c, i, n, this))
heappush(q, (c+h, i, n, this))
return float('inf'), None, []