@@ -1,7 +1,9 @@ | |||
import collections | |||
def render(grid, brush): | |||
def render(grid, brush=None): | |||
if brush is None: | |||
brush = {v: v for v in grid.values()} | |||
if isinstance(brush, str): | |||
brush = {i: c for i, c in enumerate(brush)} | |||
xmin, *_, xmax = sorted(int(p.real) for p in grid) | |||
@@ -15,25 +17,29 @@ def render(grid, brush): | |||
return rendered | |||
def bsearch(fn, goal, lo, hi): | |||
while hi - lo > 1: | |||
mid = lo + (hi - lo) // 2 | |||
if goal < fn(mid): | |||
lo, hi = lo, mid | |||
else: | |||
lo, hi = mid, hi | |||
# check | |||
a, b = fn(lo), fn(hi) | |||
assert a <= goal, 'lower bound too high' | |||
assert goal <= b, 'higher bound too low' | |||
return lo | |||
def read_image(text): | |||
grid = collections.defaultdict(str) | |||
for y, line in enumerate(text.splitlines()): | |||
for x, cell in enumerate(line): | |||
grid[complex(x, y)] = cell | |||
return grid | |||
def shortest_path(start, end, move): | |||
seen = {} | |||
edge = {start: None} | |||
while end not in seen: | |||
seen.update(edge) | |||
edge = { | |||
adj: pos | |||
for pos in edge | |||
for adj in move(pos) | |||
if adj not in seen | |||
} | |||
if not edge: | |||
raise RuntimeError('Path not found', seen) | |||
path = [] | |||
while end: | |||
path.append(end) | |||
end = seen[end] | |||
return path[::-1] |
@@ -3,8 +3,6 @@ import math | |||
import re | |||
import sys | |||
from toolkit import bsearch | |||
text = sys.stdin.read() | |||
cookbook = {} | |||
@@ -29,5 +27,21 @@ def fuel_to_ore(state): | |||
return state | |||
def bsearch(fn, goal, lo, hi): | |||
while hi - lo > 1: | |||
mid = lo + (hi - lo) // 2 | |||
if goal < fn(mid): | |||
lo, hi = lo, mid | |||
else: | |||
lo, hi = mid, hi | |||
# check | |||
a, b = fn(lo), fn(hi) | |||
assert a <= goal, 'lower bound too high' | |||
assert goal <= b, 'higher bound too low' | |||
return lo | |||
print(fuel_to_ore({'FUEL': 1})['ORE']) | |||
print(bsearch(lambda n: fuel_to_ore({'FUEL': n})['ORE'], 1E12, 1, 10_000_000)) |
@@ -0,0 +1,119 @@ | |||
import collections | |||
import itertools | |||
import sys | |||
from toolkit import read_image, render | |||
def read_in(text): | |||
grid = read_image(text) | |||
elements = {v: k for k, v in grid.items()} | |||
keys = frozenset(k for k in elements if 'a' <= k <= 'z') | |||
return dict(grid), elements, keys | |||
def move(pos): | |||
for ori in [1, -1, 1j, -1j]: | |||
if grid[pos + ori] != '#': | |||
yield pos + ori | |||
def shortest_path(start, end, move): | |||
seen = {} | |||
edge = {start: None} | |||
while end not in seen: | |||
edge = { | |||
adj: pos | |||
for pos in edge | |||
for adj in move(pos) | |||
if adj not in seen | |||
} | |||
seen.update(edge) | |||
if not edge: | |||
raise RuntimeError('No path found') | |||
path = [] | |||
while end != start: | |||
path.append(end) | |||
end = seen[end] | |||
return path | |||
def trim(states): | |||
groups = collections.defaultdict(list) | |||
for state in states: | |||
tips = frozenset(seq[-1] for seq in state) | |||
bulk = frozenset(' '.join(state)) | |||
groups[bulk, tips].append(state) | |||
return [min(groups[k], key=calc_total) for k in groups] | |||
def precalc_moves(entrances): | |||
requirements = collections.defaultdict(dict) | |||
lengths = collections.defaultdict(dict) | |||
for a, b in itertools.permutations(keys.union(entrances), 2): | |||
try: | |||
path = shortest_path(elements[a], elements[b], move) | |||
except RuntimeError: | |||
continue | |||
lengths[a][b] = len(path) | |||
requirements[a][b] = { | |||
e.lower() | |||
for e, v in elements.items() | |||
if e.isupper() | |||
and v in path | |||
} | |||
return requirements, lengths | |||
def calc_total(state): | |||
return sum(sum( | |||
lengths[a][b] | |||
for a, b in zip(s, s[1:]) | |||
if {a, b} < set(lengths) | |||
) for s in state) | |||
def mod_grid(text): | |||
grid, elements, keys = read_in(text) | |||
pos = elements['@'] | |||
grid[pos] = '#' | |||
for char, ori in zip('*&^@', [1, -1, 1j, -1j]): | |||
grid[pos + ori] = '#' | |||
grid[pos + ori + ori * 1j] = char | |||
return render(grid, {k: k for k in grid.values()}) | |||
def solve(text): | |||
global grid, elements, keys, lengths | |||
grid, elements, keys = read_in(text) | |||
stacks = {k for k in elements if not k.isalnum()} - {'#', '.'} | |||
requirements, lengths = precalc_moves(stacks) | |||
states = [stacks] | |||
for _ in range(len(keys)): | |||
states = trim( | |||
state - {seq} | {seq + b} | |||
for state in states | |||
for seq in state | |||
for b, reqs in requirements[seq[-1]].items() | |||
if b not in seq and reqs.issubset(''.join(state)) | |||
) | |||
final = min(states, key=calc_total) | |||
print(' '.join(sorted(final))) | |||
print(calc_total(final)) | |||
text = sys.stdin.read() | |||
text = ''' | |||
############# | |||
#g#f.D#..h#l# | |||
#F###e#E###.# | |||
#dCba...BcIJ# | |||
#####.@.##### | |||
#nK.L...G...# | |||
#M###N#H###.# | |||
#o#m..#i#jk.# | |||
############# | |||
''' | |||
solve(text) | |||
text = mod_grid(text) | |||
solve(text) |
@@ -0,0 +1,19 @@ | |||
import sys | |||
from intcode import compute | |||
def check(x, y): | |||
return next(compute(text, iter([x, y]))) | |||
text = sys.stdin.read() | |||
tiles = {(x, y) for x in range(50) for y in range(50) if check(x, y)} | |||
print(len(tiles)) | |||
x, y = 0, 0 | |||
while not check(x + 99, y): | |||
y += 1 | |||
while not check(x, y + 99): | |||
x += 1 | |||
print(x * 10_000 + y) |
@@ -0,0 +1,66 @@ | |||
import itertools | |||
import sys | |||
from toolkit import read_image, shortest_path | |||
def move(pos): | |||
for ori in [1, -1, 1j, -1j]: | |||
adj = pos + ori | |||
if adj in grid: | |||
yield adj | |||
if adj in duals: | |||
yield from move(duals[adj]) | |||
def move_with_depth(state): | |||
pos, level = state | |||
for ori in [1, -1, 1j, -1j]: | |||
adj = pos + ori | |||
if adj in grid: | |||
yield adj, level | |||
if adj in duals: | |||
name = grid[adj] | |||
if adj in inner: | |||
yield from move_with_depth((duals[adj], level + 1)) | |||
elif (level > 0) and (name not in {'AA', 'ZZ'}): | |||
yield from move_with_depth((duals[adj], level - 1)) | |||
# base | |||
text = sys.stdin.read() | |||
grid = {pos: val for pos, val in read_image(text).items() if val not in ' #'} | |||
for pos, val in grid.copy().items(): | |||
if val.isupper(): | |||
for im in [1, 1j]: | |||
A, B, C = [pos - im, pos, pos + im] | |||
seq = ''.join(grid.get(p, ' ') for p in [A, B, C]) | |||
if seq.endswith('.'): | |||
grid[B] = grid.pop(A) + grid.pop(B) | |||
if seq.startswith('.'): | |||
grid[B] = grid.pop(B) + grid.pop(C) | |||
elements = {v: k for k, v in grid.items()} | |||
# matrix | |||
duals = {} | |||
portals = {pos for pos, cell in grid.items() if cell.isupper()} | |||
for pA, pB in itertools.combinations(portals, 2): | |||
if grid[pA] == grid[pB]: | |||
duals[pA] = pB | |||
duals[pB] = pA | |||
# part1 | |||
start, end = elements['AA'], elements['ZZ'] | |||
path = shortest_path(start, end, move) | |||
print(len(path) - 3) | |||
# categorize portals | |||
_, *xmid, _ = sorted({p.real for p in portals}) | |||
_, *ymid, _ = sorted({p.imag for p in portals}) | |||
inner = {pos for pos in portals if pos.real in xmid and pos.imag in ymid} | |||
outer = set(portals) - inner | |||
# part2 | |||
start, end = (elements['AA'], 0), (elements['ZZ'], 0) | |||
path = shortest_path(start, end, move_with_depth) | |||
print(len(path) - 3) |