23_amphipod.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env python3
  2. import sys
  3. from heapq import heappop, heappush
  4. def parse(f):
  5. assert f.readline() == '#############\n'
  6. hallway = f.readline().rstrip().replace('#', '')
  7. top = f.readline().rstrip().replace('#', '')
  8. bottom = f.readline().strip().replace('#', '')
  9. return ''.join(map(''.join, zip(top, bottom))) + hallway
  10. def swap(s, i, j):
  11. if i > j: i, j = j, i
  12. return s[:i] + s[j] + s[i + 1:j] + s[i] + s[j + 1:]
  13. def walk(src, dst):
  14. step = -1 if src > dst else 1
  15. return range(src + step, dst + step, step)
  16. def into_hallway(state, door, rsize):
  17. first_door = 4 * rsize + 2
  18. doors = tuple(range(first_door, first_door + 8, 2))
  19. for end in (4 * rsize, len(state) - 1):
  20. for dist, i in enumerate(walk(door, end)):
  21. if state[i] != '.':
  22. break
  23. if i not in doors:
  24. yield dist + 1, i
  25. def steps(state, rsize):
  26. for room, expect in zip(range(0, 4 * rsize, rsize), 'ABCD'):
  27. door = 4 * rsize + 2 * (room // rsize + 1)
  28. for depth, cell in enumerate(state[room:room + rsize]):
  29. if cell != '.':
  30. if cell != expect or any(state[room + d] != expect
  31. for d in range(depth + 1, rsize)):
  32. for door_dist, hall in into_hallway(state, door, rsize):
  33. yield door_dist + depth + 1, room + depth, hall
  34. break
  35. for hall in range(4 * rsize, len(state)):
  36. apod = state[hall]
  37. if apod != '.':
  38. room = 'ABCD'.index(apod) * rsize
  39. door = 4 * rsize + 2 * (room // rsize + 1)
  40. if all(state[i] == '.' for i in walk(hall, door)):
  41. door_dist = abs(hall - door)
  42. for depth in range(rsize - 1, -1, -1):
  43. cell = state[room + depth]
  44. if cell == '.':
  45. yield door_dist + depth + 1, hall, room + depth
  46. elif cell != apod:
  47. break
  48. def organize(state, rsize):
  49. work = [(0, state)]
  50. seen = {state: 0}
  51. final = ''.join(x * rsize for x in 'ABCD') + '...........'
  52. while work:
  53. energy, state = heappop(work)
  54. if state == final:
  55. return energy
  56. for dist, src, dst in steps(state, rsize):
  57. moved = swap(state, src, dst)
  58. moved_energy = energy + dist * 10 ** 'ABCD'.index(state[src])
  59. if seen.get(moved, 1 << 32) > moved_energy:
  60. heappush(work, (moved_energy, moved))
  61. seen[moved] = moved_energy
  62. def extend(state, insert):
  63. return ''.join(state[2 * i] + ext + state[2 * i + 1]
  64. for i, ext in enumerate(insert)) + state[8:]
  65. state = parse(sys.stdin)
  66. print(organize(state, 2))
  67. print(organize(extend(state, ('DD', 'CB', 'BA', 'AC')), 4))