No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

133 líneas
3.4KB

  1. import collections
  2. import functools
  3. import itertools
  4. import math
  5. import re
  6. import sys
  7. def render(grid):
  8. lines = []
  9. for y in range(size):
  10. groups = [tiles[grid[y, x]].splitlines()[1:-1] for x in range(size)]
  11. for lns in zip(*groups):
  12. lines.append(''.join(ln[1:-1] for ln in lns))
  13. return '\n'.join(lines)
  14. def get_borders(content):
  15. a, *_, b = content.splitlines()
  16. c, *_, d = [''.join(ln) for ln in zip(*content.splitlines())]
  17. return [a, b, c, d, a[::-1], b[::-1], c[::-1], d[::-1]]
  18. def transpose(string):
  19. return '\n'.join(''.join(ln) for ln in zip(*string.splitlines()))
  20. def flipV(string):
  21. return '\n'.join(ln for ln in string.splitlines()[::-1])
  22. def flipH(string):
  23. return '\n'.join(ln[::-1] for ln in string.splitlines())
  24. def rot90(string):
  25. return flipH(transpose(string))
  26. def noop(string):
  27. return string
  28. def variants(string):
  29. alts = [noop, flipH], [noop, flipV], [noop, transpose]
  30. for ops in itertools.product(*alts):
  31. yield functools.reduce(lambda s, f: f(s), ops, string)
  32. def UD(A, B):
  33. return A.splitlines()[-1] == B.splitlines()[0]
  34. def LR(A, B):
  35. return UD(rot90(A), rot90(B))
  36. def search_in(source, target):
  37. source_lns = source.splitlines()
  38. target_lns = target.splitlines()
  39. h, w = len(target_lns), len(target_lns[0])
  40. out = []
  41. for y in range(len(source_lns)):
  42. for x in range(len(source_lns[0])):
  43. window = '\n'.join(ln[x:x + w] for ln in source_lns[y:y + h])
  44. if re.fullmatch(target, window):
  45. out.append((x, y))
  46. return out
  47. def reconcile(A, B, condition):
  48. tiles[A], tiles[B] = next(
  49. (X, Y)
  50. for X, Y in itertools.product(variants(tiles[A]), variants(tiles[B]))
  51. if condition(X, Y)
  52. )
  53. text = sys.stdin.read().strip()
  54. tiles = {}
  55. borders = {}
  56. for line in text.split('\n\n'):
  57. title, content = line.split(':\n')
  58. tid = int(title.split()[1])
  59. tiles[tid] = content
  60. borders[tid] = get_borders(content)
  61. size = int(len(tiles) ** 0.5)
  62. adj = collections.defaultdict(set)
  63. for (aid, A), (bid, B) in itertools.combinations(borders.items(), 2):
  64. if set(A) & set(B):
  65. adj[aid].add(bid)
  66. adj[bid].add(aid)
  67. corners = [v for v, ks in adj.items() if len(ks) == 2]
  68. print(math.prod(corners))
  69. A = min(corners)
  70. B, C = adj.pop(A)
  71. grid = {(y, x): None for x in range(size) for y in range(size)}
  72. grid[0, 0] = A
  73. grid[0, 1] = B
  74. grid[1, 0] = C
  75. reconcile(A, B, condition=LR)
  76. reconcile(A, C, condition=UD)
  77. seen = {A, B, C}
  78. for z in range(2, 2 * size - 1):
  79. # determine new pieces from intersection between 2 neighbors
  80. for y, x in {(y, z - y) for y in range(1, z)}.intersection(grid):
  81. U, L = (y - 1, x), (y, x - 1)
  82. grid[y, x], = adj[grid[U]] & adj[grid[L]] - seen
  83. reconcile(grid[L], grid[y, x], condition=LR)
  84. seen.add(grid[y, x])
  85. # determine new piece from only one alternative left
  86. for y, x in {(0, z), (z, 0)}.intersection(grid):
  87. opts = {(y - 1, x): UD, (y, x - 1): LR}
  88. P, fn = next(p for p in opts.items() if p[0] in grid)
  89. grid[y, x], = adj[grid[P]] - seen
  90. reconcile(grid[P], grid[y, x], condition=fn)
  91. seen.add(grid[y, x])
  92. monster = '''
  93. ..................#.
  94. #....##....##....###
  95. .#..#..#..#..#..#...
  96. '''[1:-1]
  97. src = render(grid)
  98. found = max([search_in(pic, monster) for pic in variants(src)], key=len)
  99. print(src.count('#') - len(found) * monster.count('#'))