Guest User

Untitled

a guest
Dec 24th, 2022
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.74 KB | None | 0 0
  1. # Input: Map of "#", ".", "^", "v", "<", ">"
  2. # Output: Starting in "." in top row,
  3. #         with each arrow moving one step per minute in its direction
  4. #           (if it hits # then it wraps around),
  5. #         and you simultaneously moving orthogonally or waiting,
  6. #         how many minutes does it take to reach "." in bottom row
  7. #           without overlapping any arrows, then return to start,
  8. #           then return to end?
  9.  
  10. # By inspection, there are no ^ or v arrows in starting or ending columns,
  11. # thus arrow pattern repeats every lcm(r, c) minutes
  12. #   (r = # rows between first/last, c = # columns between first/last)
  13. # so we can compute and store each of those states
  14.  
  15. import math
  16. from functools import cache
  17.  
  18. initial_grid = []
  19.  
  20. input_data = open(r"c:\users\edward\desktop\personal\aoc2022\24_input.txt")
  21. for input_line in input_data.read().splitlines():
  22.     input_line = input_line.replace("\n", "")
  23.     initial_grid.append(input_line)
  24.  
  25. number_interior_rows = len(initial_grid) - 2
  26. number_interior_columns = len(initial_grid[0]) - 2
  27. number_minutes = math.lcm(number_interior_rows, number_interior_columns)
  28.  
  29. initial_row = initial_grid[0]
  30. initial_column = -1
  31. for c in range(0, len(initial_row)):
  32.     if initial_row[c] == ".":
  33.         initial_column = c
  34.         break
  35.  
  36. final_row = initial_grid[len(initial_grid) - 1]
  37. final_column = -1
  38. for c in range(0, len(final_row)):
  39.     if final_row[c] == ".":
  40.         final_column = c
  41.         break
  42.  
  43. grids = []
  44. for minute in range(0, number_minutes):
  45.     blocked_positions = []
  46.     for r in range(1, number_interior_rows + 1):
  47.         for c in range(1, number_interior_columns + 1):
  48.             character = initial_grid[r][c]
  49.             if character == ".":
  50.                 continue
  51.             nr, nc = r, c
  52.             if character == "^":
  53.                 nr -= minute
  54.                 while nr < 1:
  55.                     nr += number_interior_rows
  56.             if character == "v":
  57.                 nr += minute
  58.                 while nr > number_interior_rows:
  59.                     nr -= number_interior_rows
  60.             if character == "<":
  61.                 nc -= minute
  62.                 while nc < 1:
  63.                     nc += number_interior_columns
  64.             if character == ">":
  65.                 nc += minute
  66.                 while nc > number_interior_columns:
  67.                     nc -= number_interior_columns
  68.             if not ([nr, nc] in blocked_positions):
  69.                 blocked_positions.append([nr, nc])
  70.     grids.append(blocked_positions)
  71.  
  72. # Backtrack from each variation of final position
  73.  
  74. final_positions = []
  75. for minute in range(0, number_minutes):
  76.     final_positions.append([number_interior_rows + 1, final_column, minute])
  77.  
  78. positions = []
  79. positions.append(final_positions)
  80.  
  81. # Is (r, c) at minute-state (m) a valid position to be?
  82. @cache
  83. def valid_position(r, c, m):
  84.     # Bottom row and not at end
  85.     if r > number_interior_rows and c != final_column:
  86.         return False
  87.     # Top row and not at start
  88.     if r < 1 and c != initial_column:
  89.         return False
  90.     # Too far left or right
  91.     if c < 1 or c > number_interior_columns:
  92.         return False
  93.     # Position occupied by blizzard
  94.     if [r, c] in grids[m]:
  95.         return False
  96.     # Otherwise OK
  97.     return True
  98.  
  99. # Minimum possible steps from current position to goal
  100. #   (Manhattan distance, if any such path avoids a collision at each step)
  101. @cache
  102. def minimum_steps(r, c, reached_end_once, returned_to_start_once):
  103.     if returned_to_start_once:
  104.         return (number_interior_rows + 1 - r) + abs(c - final_column)
  105.     if reached_end_once:
  106.         return r + abs(c - initial_column) + (number_interior_rows + 1) + abs(initial_column - final_column)
  107.     return (number_interior_rows + 1 - r) + abs(c - final_column) + (2 * (number_interior_rows + 1) + abs(initial_column - final_column))
  108.  
  109. # Perform best-first search
  110. # Begin at the beginning
  111. # Key of paths = minutes elapsed + minimum number of minutes still needed
  112.  
  113. paths = {}
  114. initial_state = [0, initial_column, 0, False, False] # row, column, minutes elapsed, reached end once, returned to start once
  115. initial_minimum_steps = minimum_steps(0, initial_column, False, False)
  116. paths[initial_minimum_steps] = [initial_state]
  117.  
  118. minimum_length = -1
  119. while minimum_length == -1:
  120.     key_of_paths_to_extend = min(paths.keys())
  121.     paths_to_extend = paths[key_of_paths_to_extend]
  122.     del paths[key_of_paths_to_extend]
  123.     for path in paths_to_extend:
  124.         r, c, m, reached_end_once, returned_to_start_once = path[0], path[1], path[2], path[3], path[4]
  125.  
  126.         if r == number_interior_rows + 1 and c == final_column and returned_to_start_once:
  127.             if minimum_length == -1 or minimum_length > m:
  128.                 minimum_length = m
  129.             continue
  130.        
  131.         nm = (m + 1) % number_minutes
  132.  
  133.         # Consider going south
  134.         nr, nc = r + 1, c
  135.         if valid_position(nr, nc, nm):
  136.             new_path = [nr, nc, m + 1, reached_end_once, returned_to_start_once]
  137.             if (not reached_end_once) and nr == number_interior_rows + 1 and c == final_column:
  138.                 new_path[3] = True
  139.             new_minimum_steps = m + minimum_steps(nr, nc, reached_end_once, returned_to_start_once)
  140.             if not (new_minimum_steps in paths.keys()):
  141.                 paths[new_minimum_steps] = [new_path]
  142.             else:
  143.                 if not (new_path in paths[new_minimum_steps]):
  144.                     paths[new_minimum_steps].append(new_path)
  145.  
  146.         # Consider going east
  147.         nr, nc = r, c + 1
  148.         if valid_position(nr, nc, nm):
  149.             new_path = [nr, nc, m + 1, reached_end_once, returned_to_start_once]
  150.             new_minimum_steps = m + minimum_steps(nr, nc, reached_end_once, returned_to_start_once)
  151.             if not (new_minimum_steps in paths.keys()):
  152.                 paths[new_minimum_steps] = [new_path]
  153.             else:
  154.                 if not (new_path in paths[new_minimum_steps]):
  155.                     paths[new_minimum_steps].append(new_path)
  156.  
  157.         # Consider going west
  158.         nr, nc = r, c - 1
  159.         if valid_position(nr, nc, nm):
  160.             new_path = [nr, nc, m + 1, reached_end_once, returned_to_start_once]
  161.             new_minimum_steps = m + minimum_steps(nr, nc, reached_end_once, returned_to_start_once)
  162.             if not (new_minimum_steps in paths.keys()):
  163.                 paths[new_minimum_steps] = [new_path]
  164.             else:
  165.                 if not (new_path in paths[new_minimum_steps]):
  166.                     paths[new_minimum_steps].append(new_path)
  167.  
  168.         # Consider going north
  169.         nr, nc = r - 1, c
  170.         if valid_position(nr, nc, nm):
  171.             new_path = [nr, nc, m + 1, reached_end_once, returned_to_start_once]
  172.             if reached_end_once and (not returned_to_start_once) and nr == 0 and c == initial_column:
  173.                 new_path[4] = True
  174.             new_minimum_steps = m + minimum_steps(nr, nc, reached_end_once, returned_to_start_once)
  175.             if not (new_minimum_steps in paths.keys()):
  176.                 paths[new_minimum_steps] = [new_path]
  177.             else:
  178.                 if not (new_path in paths[new_minimum_steps]):
  179.                     paths[new_minimum_steps].append(new_path)
  180.  
  181.         # Consider standing still
  182.         nr, nc = r, c
  183.         if valid_position(nr, nc, nm):
  184.             new_path = [nr, nc, m + 1, reached_end_once, returned_to_start_once]
  185.             new_minimum_steps = m + minimum_steps(nr, nc, reached_end_once, returned_to_start_once)
  186.             if not (new_minimum_steps in paths.keys()):
  187.                 paths[new_minimum_steps] = [new_path]
  188.             else:
  189.                 if not (new_path in paths[new_minimum_steps]):
  190.                     paths[new_minimum_steps].append(new_path)
  191.  
  192. print (minimum_length)
  193.  
Add Comment
Please, Sign In to add comment