day25_part1.py (3835B)
1 #!/usr/bin/env python 2 """Advent of Code 2021, day 25 (part 1): Sea Cucumber 3 Simulate the movement of sea cucumbers""" 4 5 # Going to use NumPy again. I'm going to represent the seafloor grid using 6 # integers, with 0 corresponding to an empty space, 1 corresponding to an 7 # east-moving sea cucumber, and 2 corresponding to a south-moving sea cucumber. 8 # E.g., the puzzle input: 9 # 10 # v...>>.vv> 11 # .vv>>.vv.. 12 # >>.>v>...v 13 # >>v>>.>.v. 14 # v>v.vv.v.. 15 # >.>>..v... 16 # .vv..>.>v. 17 # v.v..>>v.v 18 # ....v..v.> 19 # 20 # Becomes: 21 # 22 # 2000110221 23 # 0221102200 24 # 1101210002 25 # 1121101020 26 # 2120220200 27 # 1011002000 28 # 0220010120 29 # 2020011202 30 # 0000200201 31 32 #from typing import Tuple, List, NewType 33 34 import numpy as np 35 36 from utils import convert_input_to_array, get_puzzle_input 37 38 EXAMPLE_INPUT = \ 39 """v...>>.vv> 40 .vv>>.vv.. 41 >>.>v>...v 42 >>v>>.>.v. 43 v>v.vv.v.. 44 >.>>..v... 45 .vv..>.>v. 46 v.v..>>v.v 47 ....v..v.> 48 """ 49 50 def parse_input(input_string: str) -> np.ndarray: 51 """Given the puzzle input, return a 2d numpy array""" 52 return convert_input_to_array( 53 input_string 54 .replace('.', '0') 55 .replace('>', '1') 56 .replace('v', '2') 57 ) 58 59 def shift_up(cucumber_map: np.ndarray) -> np.ndarray: 60 """Return the entire map shifted down""" 61 return np.vstack((cucumber_map[1:, :], cucumber_map[0, :])) 62 63 def shift_down(cucumber_map: np.ndarray) -> np.ndarray: 64 """Return the entire map shifted down""" 65 return np.vstack((cucumber_map[-1, :], cucumber_map[0:-1, :])) 66 67 def is_empty(cucumber_map: np.ndarray, direction: str) -> np.ndarray: 68 """Given a cucumber map, check if the adjacent space in the given direction 69 is empty""" 70 if direction == 'east': 71 return np.transpose(is_empty(np.transpose(cucumber_map), 'south')) 72 # So actually we just need to check that the space to the south is empty 73 # (remembering that we wrap around to the top row from the bottom 74 assert direction == 'south' 75 return shift_up(cucumber_map) == 0 76 77 def shift_selected(cucumber_map: np.ndarray, movers: np.ndarray) -> np.ndarray: 78 """Shift the selected elements down one row""" 79 new_map = cucumber_map.copy() 80 new_map[shift_down(movers).nonzero()] = cucumber_map[movers.nonzero()] 81 new_map[movers.nonzero()] = 0 82 return new_map 83 84 def move_herd(cucumber_map: np.ndarray, direction: str) -> np.ndarray: 85 """Move the herd in the given direction""" 86 movers = is_empty(cucumber_map, direction) 87 if direction == 'east': 88 movers &= cucumber_map == 1 89 new_map = np.transpose(shift_selected(np.transpose(cucumber_map), 90 np.transpose(movers))) 91 elif direction == 'south': 92 movers &= cucumber_map == 2 93 new_map = shift_selected(cucumber_map, movers) 94 else: 95 raise ValueError('Direction must be "east" or "south"') 96 return new_map 97 98 def step_herd(cucumber_map: np.ndarray) -> np.ndarray: 99 """Move the whole herd east, and then south""" 100 return move_herd(move_herd(cucumber_map, 'east'), 'south') 101 102 def run_until_stuck(cucumber_map: np.ndarray) -> int: 103 """Keep stepping the herd through its moves until it stops moving""" 104 herd_position = cucumber_map 105 for step_number in range(1_000_000): 106 new_position = step_herd(herd_position) 107 if (new_position == herd_position).all(): 108 return step_number+1 109 herd_position = new_position 110 raise RuntimeError('Failed to lock up after very many steps') 111 112 def solve_puzzle(input_string: str) -> int: 113 """Return the numeric solution to the puzzle""" 114 return run_until_stuck(parse_input(input_string)) 115 116 def main() -> None: 117 """Run when the file is called as a script""" 118 assert solve_puzzle(EXAMPLE_INPUT) == 58 119 print("Steps for cucumbers to become stuck:", 120 solve_puzzle(get_puzzle_input(25))) 121 122 if __name__ == "__main__": 123 main()