Advent of Code 2016 solutions https://adventofcode.com/2016/
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/python3
  2. """Solution for day 11 of Advent of Code 2016.
  3. Performs a breadth-first search of possible moves. Because of the "elevator stops at each floor to charge" mechanic,
  4. moves are treated as up or down a single floor.
  5. To avoid backtracking, a set of previously visited states is maintained. Before being recorded, each state is
  6. 'serialised' by stripping away the element names in a deterministic manner. This is because a puzzle with a
  7. Foo-generator and Bar-chip on floor 1 and a Bar-generator and Foo-chip on floor 2, has an identical solution to
  8. a puzzle with a Bar-Generator and Foo-chip on floor 1, and a Foo-generator and Bar-chip on floor 2. That is, the
  9. elements don't matter, just the relative positions of the pairs. This dramatically reduces the number of states
  10. that have to be checked.
  11. """
  12. import itertools, re
  13. # Marker used to show the position of the lift
  14. lift = '*YOU ARE HERE*'
  15. # Read the input
  16. with open('data/11.txt', 'r') as file:
  17. lines = list(map(str.strip, file.readlines()))
  18. floors = [re.findall(r'\b(\S+ (?:generator|microchip))\b', line) for line in lines]
  19. floors[0].append(lift)
  20. # Return the elements of the chips or generators in the given lists
  21. chips = lambda items: set([item.split('-')[0] for item in items if item.endswith('microchip')])
  22. genrs = lambda items: set([item.split(' ')[0] for item in items if item.endswith('generator')])
  23. # Verify that if there are generators, then all microchips present are paired
  24. valid_floor = lambda floor: not len(genrs(floor)) or not len(chips(floor) - genrs(floor))
  25. valid_layout = lambda layout: False not in [valid_floor(floor) for floor in layout]
  26. # We win when everything is on the last floor (i.e., nothing is on the other floors)
  27. target = lambda layout: sum(len(floor) for floor in layout[:-1]) == 0
  28. # Returns the floor/floor index the lift is currently on
  29. my_floor = lambda layout: next(floor for floor in layout if lift in floor)
  30. my_floor_index = lambda layout: next(i for i, floor in enumerate(layout) if lift in floor)
  31. # Returns just the items on a floor (not the lift)
  32. items = lambda floor: set(floor) - {lift}
  33. # Returns an enumeration of sets of items that could potentially be picked up (any combo of 1 or 2 items)
  34. pickups = lambda items: map(set, itertools.chain(itertools.combinations(items, 2), itertools.combinations(items, 1)))
  35. # Returns an enumeration of possible destinations for the lift (up or down one floor)
  36. dests = lambda layout: filter(lambda i: 0 <= i < len(floors), [my_floor_index(layout) + 1, my_floor_index(layout) - 1])
  37. # Returns an enumeration of possible moves that could be made from the given state
  38. moves = lambda layout: itertools.product(pickups(items(my_floor(layout))), dests(layout))
  39. # Finds a floor that contains the given item
  40. find = lambda item, layout: next(i for i, floor in enumerate(layout) if item in floor)
  41. # Performs a breadth-first search over all moves for the given layout in order to find the number
  42. # of steps needed to get to a winning state.
  43. def run(floors):
  44. # The available types depends on the input (and thus differs between calls to the run function),
  45. # so we have to calculate it here, and make the serialise() and domoves() functions closures
  46. # over this list.
  47. types = [chip.split(' ')[0] for chip in itertools.chain.from_iterable(chips(items(floor)) for floor in floors)]
  48. # Serialises a layout into a string, for easy storage.
  49. # Items are replaced with numeric identifiers, determined based on position of the generator and
  50. # chip of that type. This means that layouts that are identical except for the elements being
  51. # swapped around serialise to the same string (as the process for moving them to the end will be
  52. # the) same.
  53. def serialise(layout):
  54. keys = sorted(types, key=lambda t: find(t + ' generator', layout) * len(layout)
  55. + find(t + '-compatible microchip', layout))
  56. mappings = {lift: '*'}
  57. for i, key in enumerate(keys):
  58. mappings['%s generator' % key] = '%iG' % i
  59. mappings['%s-compatible microchip' % key] = '%iM' % i
  60. return '|'.join(''.join(sorted(mappings[item] for item in floor)) for floor in layout)
  61. # Evaluates each possible move for the given layout.
  62. # Moves are checked to ensure they're valid and serialised to ensure they haven't been visited
  63. # Returns a list of new layouts for the next step, or False if a solution was encountered
  64. def domoves(layout, steps):
  65. queued = []
  66. for items, to in moves(layout):
  67. items = set(items).union({lift})
  68. new_layout = [set(floor) - items for floor in layout]
  69. new_layout[to] |= items
  70. if valid_layout(new_layout):
  71. serialised = serialise(new_layout)
  72. if serialised not in distances:
  73. distances.add(serialised)
  74. queued.append(new_layout)
  75. if target(new_layout):
  76. return False
  77. return queued
  78. # Run repeated iterations until we hit a winning result, then immediately returns the step
  79. # count.
  80. distances = {serialise(floors)}
  81. step = 1
  82. queued = [floors]
  83. while True:
  84. next_queue = []
  85. for layout in queued:
  86. res = domoves(layout, step)
  87. if res == False:
  88. return step
  89. next_queue.extend(res)
  90. queued = next_queue
  91. step += 1
  92. print("Part 1: %s" % run(floors))
  93. floors[0].extend(['elerium generator', 'elerium-compatible microchip',
  94. 'dilithium generator', 'dilithium-compatible microchip'])
  95. print("Part 2: %s" % run(floors))