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.

24.py 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. #!/usr/bin/python3
  2. """Solution for day 24 of Advent of Code 2016.
  3. Another day, another breadth-first search! I split this problem into two stages: first, computing the distance
  4. between every combination of points in the maze, and, second, finding the shortest route using those paths.
  5. Finding the distances is just a simple BFS. As we only care about distance, each pair of nodes is only calculated
  6. once (i.e., it stores a distance for, e.g., (0 -> 2) but not (2 -> 0)). Pairs are stored sorted.
  7. To find all the possible routes, we just need a call to itertools.permutations for the middle section (ignoring '0').
  8. The '0' stop(s) are then added around the permutation, and the total distance calculated by chunking them into pairs
  9. and looking them up in the distances table. This is done in the route_length method.
  10. For example, with the example maze with 5 points we compute and store 10 distances:
  11. 0 -> 1, 0 -> 2, 0 -> 3, 0 -> 4, 1 -> 2, 1 -> 3, 1 -> 4, 2 -> 3, 2-> 4, 3-> 4
  12. Which become tuple keys in our distances dictionary:
  13. (0, 1): a, (0, 2): b, ... (3, 4): z
  14. Then to find the route we start with all 24 possible routes:
  15. 01234, 01243, 01324, 01342, 01423, 01432, 02134, etc...
  16. Each route is chunked into pairs:
  17. 01234 ==> (0, 1), (1, 2), (2, 3), (3, 4)
  18. And the pairs are then looked up in the dictionary and summed.
  19. The problem could in theory be solved using one large BFS but it would be fairly complicated as you'd need to prevent
  20. backtracking generally, while still allowing it after reaching a new numbered location. You'd also end up recalculating
  21. the distances between many places unless you did some clever optimisations. Pre-computing the distances also makes part
  22. 2 very easy.
  23. """
  24. import functools
  25. import itertools
  26. import operator
  27. def find(maze, num):
  28. for y, line in enumerate(maze):
  29. if num in line:
  30. return line.index(num), y
  31. def navigable(maze, location):
  32. return maze[location[1]][location[0]] != '#'
  33. def directions(location):
  34. yield (location[0], location[1] + 1)
  35. yield (location[0], location[1] - 1)
  36. yield (location[0] + 1, location[1])
  37. yield (location[0] - 1, location[1])
  38. def moves(maze, start):
  39. return set(filter(lambda loc: navigable(maze, loc), directions(start)))
  40. def distance(maze, start_num, end_num):
  41. start = find(maze, start_num)
  42. end = find(maze, end_num)
  43. visited = set()
  44. queue = [start]
  45. steps = 0
  46. while len(queue):
  47. new_queue = functools.reduce(operator.or_, [moves(maze, target) for target in queue])
  48. queue = new_queue - visited
  49. visited |= queue
  50. steps += 1
  51. if end in queue:
  52. return steps
  53. def route_length(distances, route):
  54. return sum(map(lambda p: distances[tuple(sorted(p))], (route[i:i +2] for i in range(0, len(route)-1))))
  55. with open('data/24.txt', 'r') as file:
  56. my_maze = list(map(str.strip, file.readlines()))
  57. points = sorted(list(x for x in itertools.chain(*my_maze) if x.isdigit()))
  58. distances = dict(((pair, distance(my_maze, *pair)) for pair in itertools.combinations(points, 2)))
  59. routes = set(itertools.permutations(points[1:]))
  60. print('Part one: %s' % min(route_length(distances, ['0'] + [*route]) for route in routes))
  61. print('Part two: %s' % min(route_length(distances, ['0'] + [*route] + ['0']) for route in routes))