You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

94 lines
2.5KB

  1. import collections
  2. import itertools
  3. import sys
  4. from functools import partial
  5. from toolkit import read_image, render, shortest_path
  6. def read_in(text):
  7. grid = read_image(text)
  8. elements = {v: k for k, v in grid.items()}
  9. keys = {c for c in elements if c.isalpha() and c.islower()}
  10. return dict(grid), elements, keys
  11. def move(grid, pos):
  12. for ori in [1, -1, 1j, -1j]:
  13. if grid[pos + ori] != '#':
  14. yield pos + ori
  15. def trim(states, metric):
  16. groups = collections.defaultdict(list)
  17. for state in states:
  18. tips = frozenset(seq[-1] for seq in state)
  19. bulk = frozenset(' '.join(state))
  20. groups[bulk, tips].append(state)
  21. return [min(groups[k], key=metric) for k in groups]
  22. def precalc_moves(entrances, keys, elements, grid):
  23. requirements = collections.defaultdict(dict)
  24. lengths = {}
  25. for a, b in itertools.combinations(keys.union(entrances), 2):
  26. try:
  27. path = shortest_path(elements[a], elements[b], partial(move, grid))
  28. *glyphs, = map(grid.get, path)
  29. except RuntimeError:
  30. continue
  31. lengths[a, b] = len(path) - 1
  32. lengths[b, a] = len(path) - 1
  33. requirements[a][b] = {c.lower() for c in glyphs if c.isupper()}
  34. requirements[b][a] = {c.lower() for c in glyphs if c.isupper()}
  35. return requirements, partial(calc_total, lengths)
  36. def calc_total(lengths, state):
  37. return sum(lengths[pair] for seq in state for pair in zip(seq, seq[1:]))
  38. def mod_grid(text):
  39. grid, elements, keys = read_in(text)
  40. pos = elements['@']
  41. grid[pos] = '#'
  42. for char, ori in zip('*&^@', [1, -1, 1j, -1j]):
  43. grid[pos + ori] = '#'
  44. grid[pos + ori + ori * 1j] = char
  45. return render(grid)
  46. def solve(text):
  47. grid, elements, keys = read_in(text)
  48. stacks = {k for k in elements if not k.isalnum()} - {'#', '.'}
  49. requirements, metric = precalc_moves(stacks, keys, elements, grid)
  50. states = [stacks]
  51. for _ in range(len(keys)):
  52. states = trim([
  53. state - {seq} | {seq + b}
  54. for state in states
  55. for seq in state
  56. for b, reqs in requirements[seq[-1]].items()
  57. if b not in seq and reqs.issubset(''.join(state))
  58. ], metric)
  59. best = min(states, key=metric)
  60. print(best)
  61. print(metric(best))
  62. text = sys.stdin.read()
  63. text = '''
  64. #############
  65. #g#f.D#..h#l#
  66. #F###e#E###.#
  67. #dCba...BcIJ#
  68. #####.@.#####
  69. #nK.L...G...#
  70. #M###N#H###.#
  71. #o#m..#i#jk.#
  72. #############
  73. '''
  74. solve(text)
  75. text = mod_grid(text)
  76. solve(text)