Advent of Code 2016 solutions https://adventofcode.com/2016/
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.

17.py 2.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. #!/usr/bin/python3
  2. """Solution for day 17 of Advent of Code 2016.
  3. Implements an A* search algorithm for finding the shortest path (which wasn't really necessary given part two!)
  4. The heapq module is used to keep the queue sorted in priority order, with the priorities calculated as the
  5. current distance travelled plus the Manhattan distance to the end (so the first path found will be the shortest).
  6. """
  7. import functools
  8. import hashlib
  9. import heapq
  10. import itertools
  11. salt = 'njfxhljp'
  12. moves = {'U': (0, -1), 'D': (0, +1), 'L': (-1, 0), 'R': (+1, 0)}
  13. def open_doors(path):
  14. """Gives a list of open doors around the current square, given the path used to reach it.
  15. Does not take into account whether doors actually exist or not.
  16. :param path: The path taken to reach the square (as a string, one char per move; e.g. 'UDUDRLRLLL')
  17. :return: Iterator of directions which have open doors
  18. """
  19. return itertools.compress('UDLR', [x > 'a' for x in hashlib.md5((salt + path).encode('UTF-8')).hexdigest()[:4]])
  20. def next_moves(coords, path):
  21. """Gets a list of all possible moves from the given position tuple.
  22. Takes in to account the maze bounds and door states.
  23. A move's "priority" is the length of the path taken so far, plus the Manhattan distance to the end goal.
  24. This allows us to use a best-first searching algorithm like A* to efficiently find paths to the end.
  25. :param coords: The current co-ordinates, as a tuple of (x, y).
  26. :param path: The path taken to reach the square (as a string, one char per move; e.g. 'UDUDRLRLLL')
  27. :return: An (unsorted) iterator of new potential positions, as tuples of (priority, co-ordinates, path)
  28. """
  29. for direction in open_doors(path):
  30. new_coords = tuple(map(sum, zip(coords, moves[direction])))
  31. if 0 <= new_coords[0] <= 3 and 0 <= new_coords[1] <= 3:
  32. yield ((len(path) + 7 - coords[0] - coords[1], new_coords, path + direction))
  33. def find_paths():
  34. """Finds all paths from the starting point of (0, 0) to the end point of (3, 3).
  35. Paths are searched in priority order, meaning the shortest path is returned first and the longest path returned
  36. last.
  37. :return: A generator which emits paths (as strings of directions, e.g. 'UDDDRRR...') from shortest to longest
  38. """
  39. queue = []
  40. heapq.heappush(queue, (6, (0, 0), ''))
  41. while len(queue):
  42. _, coords, path = heapq.heappop(queue)
  43. if coords == (3, 3):
  44. yield path
  45. else:
  46. for move in next_moves(coords, path):
  47. heapq.heappush(queue, move)
  48. paths = find_paths()
  49. print('Step 1: %s' % next(paths))
  50. print('Step 2: %s' % len(functools.reduce(lambda x, y: y, paths)))