Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

133 Zeilen
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('#'))