AdventOfCode/2019/day18.py

135 lines
4.1 KiB
Python
Raw Normal View History

2019-12-18 18:01:42 +01:00
import string
2019-12-19 00:21:45 +01:00
import sys
2019-12-18 18:01:42 +01:00
2019-12-18 12:09:12 +01:00
from grid import Grid
2019-12-18 18:01:42 +01:00
from vector import v2a
EX1 = [
"#########",
"#b.A.@.a#",
"#########",
]
EX2 = [
"########################",
"#f.D.E.e.C.b.A.@.a.B.c.#",
"######################.#",
"#d.....................#",
"########################",
]
2019-12-18 12:09:12 +01:00
def main():
with open("day18.txt", "rt") as input_file:
lines = input_file.readlines()
2019-12-19 00:21:45 +01:00
lab = Lab(lines=lines)
2019-12-18 12:09:12 +01:00
lab.dumb_print()
2019-12-18 18:01:42 +01:00
lab.find_positions()
2019-12-19 00:21:45 +01:00
states = [(lab.start, frozenset(), 0)]
state_cache = Grid(value_factory=dict)
num_keys = len(lab.key_pos)
min_steps = 2**32
num_processed = 0
while states:
num_processed += 1
pos, obtained, total_steps = states.pop(0)
if len(obtained) == num_keys:
print(f"All keys obtained in {total_steps}.")
min_steps = min(min_steps, total_steps)
continue
for key_name, bt in lab.get_missing_backtracks(pos, keys=obtained).items():
steps, required, found = bt
if len(required - obtained) > 0:
continue
next_pos = lab.key_pos[key_name]
next_obtained = obtained | found | {key_name}
next_steps = total_steps + steps
old_bests = state_cache.getv(next_pos[0], next_pos[1])
if next_obtained not in old_bests or old_bests[next_obtained] > next_steps:
print("o", end="")
states.append((next_pos, next_obtained, next_steps))
old_bests[next_obtained] = next_steps
print(".", end="")
if num_processed % 100 == 0:
print(f"[{num_processed}/{len(states)}]", end="")
sys.stdout.flush()
print(f"Minimal number of steps to get all keys: {min_steps}.")
2019-12-18 12:09:12 +01:00
class Lab(Grid):
TILE_PATH = "."
TILE_WALL = "#"
TILE_START = "@"
2019-12-18 18:01:42 +01:00
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, value_factory=lambda: " ")
self.start = None
self.key_pos = {}
self.door_pos = {}
def find_positions(self):
for x, y, v in self.values_gen():
if v == Lab.TILE_START:
self.start = (x, y)
if v in string.ascii_lowercase:
self.key_pos[v] = (x, y)
if v in string.ascii_uppercase:
self.door_pos[v] = (x, y)
self.setv(self.start[0], self.start[1], Lab.TILE_PATH)
2019-12-19 00:21:45 +01:00
def get_missing_backtracks(self, start, keys=set()):
backtracks = {}
for kname, kpos in self.key_pos.items():
if kname in keys:
continue
path = self.path(start, kpos, keys=keys)
if path is None:
continue
backtracks[kname] = self.backtrack(path, kpos, start)
return backtracks
def path(self, s, e, keys=set()):
2019-12-18 18:01:42 +01:00
discovered = Grid(value_factory=bool)
discovered.setv(s[0], s[1], True)
parents = {}
q = [s]
while q:
pos = q.pop(0)
if pos == e:
return parents
nears = self.near_objects(pos)
for near_pos, near_tile in nears.items():
if near_tile == Lab.TILE_WALL:
continue
2019-12-19 00:21:45 +01:00
if near_tile in string.ascii_uppercase and near_tile.lower() not in keys:
continue
2019-12-18 18:01:42 +01:00
if discovered.getv(near_pos[0], near_pos[1]):
continue
discovered.setv(near_pos[0], near_pos[1], True)
parents[near_pos] = pos
q.append(near_pos)
2019-12-19 00:21:45 +01:00
return None
2019-12-18 18:01:42 +01:00
def backtrack(self, parents, pos, end):
count = 0
requirements = set()
keys_found = set()
while pos != end:
pos = parents[pos]
count += 1
tile = self.getv(pos[0], pos[1])
if tile in string.ascii_uppercase:
requirements.add(tile.lower())
elif tile in string.ascii_lowercase:
keys_found.add(tile)
2019-12-19 00:21:45 +01:00
return count, requirements, frozenset(keys_found)
2019-12-18 18:01:42 +01:00
2019-12-18 12:09:12 +01:00
if __name__ == "__main__":
main()