Ver código fonte

2020/20

master
Roderic Day 4 anos atrás
pai
commit
5d16a2acc2
2 arquivos alterados com 52 adições e 86 exclusões
  1. +3
    -0
      makefile
  2. +49
    -86
      y2020/p20.py

+ 3
- 0
makefile Ver arquivo

@@ -7,6 +7,9 @@ export
main: venv/ $(DATA)
@cat $(DATA) | venv/bin/python -u $(FILE)

flake: venv/
venv/bin/flake8 --exclude=venv/

$(DATA):
@echo $(DATA) | venv/bin/python -c "import toolkit; toolkit.get_dat()" $(DATA)


+ 49
- 86
y2020/p20.py Ver arquivo

@@ -1,4 +1,3 @@
import collections
import functools
import itertools
import math
@@ -8,125 +7,89 @@ import sys

def render(grid):
lines = []
for y in range(size):
groups = [tiles[grid[y, x]].splitlines()[1:-1] for x in range(size)]
for lns in zip(*groups):
for row in grid:
for lns in zip(*[cell.splitlines()[1:-1] for cell in row]):
lines.append(''.join(ln[1:-1] for ln in lns))
return '\n'.join(lines)


def get_borders(content):
a, *_, b = content.splitlines()
c, *_, d = [''.join(ln) for ln in zip(*content.splitlines())]
return [a, b, c, d, a[::-1], b[::-1], c[::-1], d[::-1]]


def transpose(string):
return '\n'.join(''.join(ln) for ln in zip(*string.splitlines()))


def flipV(string):
return '\n'.join(ln for ln in string.splitlines()[::-1])


def flipH(string):
return '\n'.join(ln[::-1] for ln in string.splitlines())


def rot90(string):
return flipH(transpose(string))


def noop(string):
return string
class Ops:
noop = lambda txt: txt # noqa
flipV = lambda txt: '\n'.join(ln for ln in txt.splitlines()[::-1]) # noqa
flipH = lambda txt: '\n'.join(ln[::-1] for ln in txt.splitlines()) # noqa
transpose = lambda txt: '\n'.join(map(''.join, zip(*txt.splitlines()))) # noqa


def variants(string):
alts = [noop, flipH], [noop, flipV], [noop, transpose]
for ops in itertools.product(*alts):
alts = [Ops.flipH, Ops.flipV, Ops.transpose]
for ops in itertools.product(*[[Ops.noop, fn] for fn in alts]):
yield functools.reduce(lambda s, f: f(s), ops, string)


def UD(A, B):
return A.splitlines()[-1] == B.splitlines()[0]


def LR(A, B):
return UD(rot90(A), rot90(B))


def search_in(source, target):
source_lns = source.splitlines()
target_lns = target.splitlines()
h, w = len(target_lns), len(target_lns[0])
out = []
for y in range(len(source_lns)):
for x in range(len(source_lns[0])):
window = '\n'.join(ln[x:x + w] for ln in source_lns[y:y + h])
if re.fullmatch(target, window):
out.append((x, y))
return out
yield (x, y)


def find(old, new):
delta = new - old
base = fixed[old]

if delta == -1: goal = [ln[0] for ln in base.splitlines()] # noqa
elif delta == 1: goal = [ln[-1] for ln in base.splitlines()] # noqa
elif delta == 1j: goal = base.splitlines()[-1] # noqa
elif delta == -1j: goal = base.splitlines()[0] # noqa

for tid, tile in tiles.items():
for var in variants(tile):

if delta == -1: found = [ln[-1] for ln in var.splitlines()] # noqa
elif delta == 1: found = [ln[0] for ln in var.splitlines()] # noqa
elif delta == 1j: found = var.splitlines()[0] # noqa
elif delta == -1j: found = var.splitlines()[-1] # noqa

def reconcile(A, B, condition):
tiles[A], tiles[B] = next(
(X, Y)
for X, Y in itertools.product(variants(tiles[A]), variants(tiles[B]))
if condition(X, Y)
)
if goal == found:
tiles.pop(tid)
grid[new] = tid
fixed[new] = var
yield tid
return


text = sys.stdin.read().strip()
tiles = {}
borders = {}
for line in text.split('\n\n'):
title, content = line.split(':\n')
tid = int(title.split()[1])
tiles[tid] = content
borders[tid] = get_borders(content)
size = int(len(tiles) ** 0.5)

adj = collections.defaultdict(set)
for (aid, A), (bid, B) in itertools.combinations(borders.items(), 2):
if set(A) & set(B):
adj[aid].add(bid)
adj[bid].add(aid)
corners = [v for v, ks in adj.items() if len(ks) == 2]
print(math.prod(corners))


A = min(corners)
B, C = adj.pop(A)

grid = {(y, x): None for x in range(size) for y in range(size)}
grid[0, 0] = A
grid[0, 1] = B
grid[1, 0] = C
reconcile(A, B, condition=LR)
reconcile(A, C, condition=UD)

seen = {A, B, C}
for z in range(2, 2 * size - 1):
# determine new pieces from intersection between 2 neighbors
for y, x in {(y, z - y) for y in range(1, z)}.intersection(grid):
U, L = (y - 1, x), (y, x - 1)
grid[y, x], = adj[grid[U]] & adj[grid[L]] - seen
reconcile(grid[L], grid[y, x], condition=LR)
seen.add(grid[y, x])

# determine new piece from only one alternative left
for y, x in {(0, z), (z, 0)}.intersection(grid):
opts = {(y - 1, x): UD, (y, x - 1): LR}
P, fn = next(p for p in opts.items() if p[0] in grid)
grid[y, x], = adj[grid[P]] - seen
reconcile(grid[P], grid[y, x], condition=fn)
seen.add(grid[y, x])
start = min(tiles, key=tiles.get)
fixed = {0: tiles.pop(start)}
grid = {0: start}
while tiles:
grid.update({
new: good
for old in list(grid)
for new in {old - 1, old + 1, old + 1j, old - 1j} - set(fixed)
for good in find(old, new)
})

xs = xmin, *_, xmax = sorted({int(p.real) for p in fixed})
ys = ymin, *_, ymax = sorted({int(p.imag) for p in fixed})
print(math.prod(grid[x + 1j * y] for x in [xmin, xmax] for y in [ymin, ymax]))

monster = '''
..................#.
#....##....##....###
.#..#..#..#..#..#...
'''[1:-1]
src = render(grid)
found = max([search_in(pic, monster) for pic in variants(src)], key=len)
src = render([[fixed[complex(x, y)] for x in xs] for y in ys])
found = max([list(search_in(pic, monster)) for pic in variants(src)], key=len)
print(src.count('#') - len(found) * monster.count('#'))

Carregando…
Cancelar
Salvar