| import collections | 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): | if isinstance(brush, str): | ||||
| brush = {i: c for i, c in enumerate(brush)} | brush = {i: c for i, c in enumerate(brush)} | ||||
| xmin, *_, xmax = sorted(int(p.real) for p in grid) | xmin, *_, xmax = sorted(int(p.real) for p in grid) | ||||
| return rendered | 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): | def read_image(text): | ||||
| grid = collections.defaultdict(str) | grid = collections.defaultdict(str) | ||||
| for y, line in enumerate(text.splitlines()): | for y, line in enumerate(text.splitlines()): | ||||
| for x, cell in enumerate(line): | for x, cell in enumerate(line): | ||||
| grid[complex(x, y)] = cell | grid[complex(x, y)] = cell | ||||
| return grid | 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] | 
| import re | import re | ||||
| import sys | import sys | ||||
| from toolkit import bsearch | |||||
| text = sys.stdin.read() | text = sys.stdin.read() | ||||
| cookbook = {} | cookbook = {} | ||||
| return 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(fuel_to_ore({'FUEL': 1})['ORE']) | ||||
| print(bsearch(lambda n: fuel_to_ore({'FUEL': n})['ORE'], 1E12, 1, 10_000_000)) | print(bsearch(lambda n: fuel_to_ore({'FUEL': n})['ORE'], 1E12, 1, 10_000_000)) | 
| 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) | 
| 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) | 
| 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) |