Advertisement
JonathanGupton

Advent of Code 2022 - Day 17 - Python

Dec 17th, 2022
310
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.91 KB | None | 0 0
  1. from __future__ import annotations
  2.  
  3. from itertools import chain
  4. from itertools import cycle
  5. from itertools import repeat
  6.  
  7.  
  8. def parse_input(filepath) -> str:
  9.     with open(filepath, "r") as f:
  10.         return f.read().strip()
  11.  
  12.  
  13. class Rock:
  14.     def __init__(self):
  15.         self.coordinates = set()
  16.  
  17.     def __str__(self):
  18.         return f"{self.__class__.__name__}({self.coordinates})"
  19.  
  20.     def __repr__(self):
  21.         return str(self)
  22.  
  23.     def max_height(self):
  24.         max_height = 0
  25.         for coordinate in self.coordinates:
  26.             max_height = max(max_height, coordinate.imag)
  27.         return max_height
  28.  
  29.     def move_left(self) -> Rock:
  30.         rock = Rock()
  31.         for coordinate in self.coordinates:
  32.             rock.coordinates.add(coordinate + complex(-1, 0))
  33.         return rock
  34.  
  35.     def move_right(self) -> Rock:
  36.         rock = Rock()
  37.         for coordinate in self.coordinates:
  38.             rock.coordinates.add(coordinate + complex(1, 0))
  39.         return rock
  40.  
  41.     def move_down(self) -> Rock:
  42.         rock = Rock()
  43.         for coordinate in self.coordinates:
  44.             rock.coordinates.add(coordinate + complex(0, -1))
  45.         return rock
  46.  
  47.     @classmethod
  48.     def flat(cls, start_x, start_y) -> Rock:
  49.         rock = Rock()
  50.         initial_configuration = [
  51.             complex(0, 0),
  52.             complex(1, 0),
  53.             complex(2, 0),
  54.             complex(3, 0),
  55.         ]
  56.         for position in initial_configuration:
  57.             position += complex(start_x, start_y)
  58.             rock.coordinates.add(position)
  59.         return rock
  60.  
  61.     @classmethod
  62.     def cross(cls, start_x, start_y):
  63.         rock = Rock()
  64.         initial_configuration = [
  65.             complex(1, 0),
  66.             complex(0, 1),
  67.             complex(1, 1),
  68.             complex(2, 1),
  69.             complex(1, 2),
  70.         ]
  71.         for position in initial_configuration:
  72.             position += complex(start_x, start_y)
  73.             rock.coordinates.add(position)
  74.         return rock
  75.  
  76.     @classmethod
  77.     def corner(cls, start_x, start_y):
  78.         rock = Rock()
  79.         initial_configuration = [
  80.             complex(0, 0),
  81.             complex(1, 0),
  82.             complex(2, 0),
  83.             complex(2, 1),
  84.             complex(2, 2),
  85.         ]
  86.         for position in initial_configuration:
  87.             position += complex(start_x, start_y)
  88.             rock.coordinates.add(position)
  89.         return rock
  90.  
  91.     @classmethod
  92.     def tall(cls, start_x, start_y):
  93.         rock = Rock()
  94.         initial_configuration = [
  95.             complex(0, 0),
  96.             complex(0, 1),
  97.             complex(0, 2),
  98.             complex(0, 3),
  99.         ]
  100.         for position in initial_configuration:
  101.             position += complex(start_x, start_y)
  102.             rock.coordinates.add(position)
  103.         return rock
  104.  
  105.     @classmethod
  106.     def square_rock(cls, start_x, start_y):
  107.         rock = Rock()
  108.         initial_configuration = [
  109.             complex(0, 0),
  110.             complex(1, 0),
  111.             complex(0, 1),
  112.             complex(1, 1),
  113.         ]
  114.         for position in initial_configuration:
  115.             position += complex(start_x, start_y)
  116.             rock.coordinates.add(position)
  117.         return rock
  118.  
  119.  
  120. class Cave:
  121.     rock_shapes = (Rock.flat, Rock.cross, Rock.corner, Rock.tall, Rock.square_rock)
  122.  
  123.     def __init__(self, width: int, moves: str):
  124.         self.moves = moves  # <><><<<>><>
  125.         self.width = width
  126.         self.rock_shapes_index = 0
  127.         self.height = 1
  128.         self._drop_height = 3
  129.         self.start_x = 2
  130.         self.rock_positions = set()
  131.         self.jet_generator = chain.from_iterable(zip(cycle(self.moves), repeat(None)))
  132.  
  133.     @property
  134.     def start_y(self):
  135.         return self.height + self._drop_height
  136.  
  137.     def get_next_rock(self) -> Rock:
  138.         r = self.rock_shapes[self.rock_shapes_index](self.start_x, self.start_y)
  139.         self.update_rock_shape_index()
  140.         return r
  141.  
  142.     def update_rock_shape_index(self):
  143.         self.rock_shapes_index = (self.rock_shapes_index + 1) % len(self.rock_shapes)
  144.  
  145.     def clear_of_collision_check(self, rock: Rock) -> bool:
  146.         if self.rock_positions & rock.coordinates:
  147.             return False
  148.         else:
  149.             for coordinate in rock.coordinates:
  150.                 if (coordinate.real == -1) or (coordinate.real == self.width):
  151.                     return False
  152.                 if coordinate.imag == 0:
  153.                     return False
  154.         return True
  155.  
  156.     def drop_rock(self):
  157.         rock = self.get_next_rock()
  158.  
  159.         for direction in self.jet_generator:
  160.             match direction:
  161.                 case "<":
  162.                     next_position = rock.move_left()
  163.                     if self.clear_of_collision_check(next_position):
  164.                         rock = next_position
  165.                 case ">":
  166.                     next_position = rock.move_right()
  167.                     if self.clear_of_collision_check(next_position):
  168.                         rock = next_position
  169.                 case _:
  170.                     next_position = rock.move_down()
  171.                     if self.clear_of_collision_check(next_position):
  172.                         rock = next_position
  173.                     else:
  174.                         self.rock_positions.update(rock.coordinates)
  175.                         height_diff = rock.max_height() + 1 - self.height
  176.                         self.height = max(self.height, rock.max_height() + 1)
  177.                         return 0 if height_diff < 1 else height_diff
  178.  
  179.  
  180. def find_pattern(heights: list[int]):
  181.     max_pattern_length = len(heights) // 2
  182.     for pattern_length in range(2, max_pattern_length):
  183.         for i in range(0, len(heights) - pattern_length):
  184.             window = heights[i : i + pattern_length]
  185.             duplicates = 0
  186.             next_start = i + pattern_length
  187.             next_stop = next_start + pattern_length
  188.             while window == heights[next_start:next_stop]:
  189.                 duplicates += 1
  190.                 if duplicates >= 3:
  191.                     return i, window
  192.                 next_start, next_stop = (
  193.                     next_start + pattern_length,
  194.                     next_stop + pattern_length,
  195.                 )
  196.  
  197.  
  198. def part_one(filepath) -> int:
  199.     moves = parse_input(filepath)
  200.     cave = Cave(width=7, moves=moves)
  201.     for i in range(2022):
  202.         cave.drop_rock()
  203.     return int(cave.height - 1)
  204.  
  205.  
  206. def part_two(filepath) -> int:
  207.     moves = parse_input(filepath)
  208.     height_changes = []
  209.     cave = Cave(width=7, moves=moves)
  210.     for i in range(20000):
  211.         height_changes.append(int(cave.drop_rock()))
  212.  
  213.     start, window = find_pattern(height_changes)
  214.     print(f"start: {start}\twindow length: {len(window)}")
  215.     left = sum(height_changes[:start])
  216.     middle, right = divmod(1000000000000 - start, len(window))
  217.     middle = middle * sum(window)
  218.     right = sum(window[:right])
  219.  
  220.     return int(left + middle + right)
  221.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement