Advertisement
Guest User

AoC 2022 - Day 17 - Python

a guest
Dec 20th, 2022
927
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.10 KB | None | 0 0
  1. """
  2. Advent of Code 2022 Day 17
  3. """
  4. import sys
  5.  
  6. from itertools import cycle
  7.  
  8. from advent_tools import get_daily_input
  9.  
  10. DAY = 17
  11.  
  12. TEST = sys.argv[1] == "test" if len(sys.argv) > 1 else False
  13.  
  14. TEST_DATA = """
  15. >>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>
  16. """
  17.  
  18. if TEST:
  19.     def get_daily_input(_):
  20.         for line in TEST_DATA.strip().split("\n"):
  21.             yield line.strip("\n")
  22.  
  23.  
  24. class RockPile:
  25.  
  26.     rock_shapes = {
  27.         "line": [
  28.             "0011110",
  29.         ],
  30.         "cross": [
  31.             "0001000",
  32.             "0011100",
  33.             "0001000",
  34.         ],
  35.         "elbow": [
  36.             "0000100",
  37.             "0000100",
  38.             "0011100",
  39.         ],
  40.         "column": [
  41.             "0010000",
  42.             "0010000",
  43.             "0010000",
  44.             "0010000",
  45.         ],
  46.         "square": [
  47.             "0011000",
  48.             "0011000",
  49.         ]
  50.     }
  51.  
  52.     def __init__(self, wind_directions: str):
  53.         self.wind_direction = cycle(wind_directions)
  54.         self.rock_shape = cycle(self.rock_shapes)
  55.         self.pile: list[int] = []
  56.  
  57.     @property
  58.     def height(self) -> int:
  59.         return len(self.pile)
  60.  
  61.     def can_shift(self, direction: str, pile_level: int, rock: list[int]) -> bool:
  62.         for r in range(len(rock)):
  63.             if (
  64.                     direction == ">" and
  65.                     ((rock[r] & 1) or (rock[r] >> 1 & self.pile[pile_level + r]))
  66.             ) or (
  67.                     direction == "<" and
  68.                     ((rock[r] & 2 ** 6) or (rock[r] << 1 & self.pile[pile_level + r]))
  69.             ):
  70.                 return False
  71.         return True
  72.  
  73.     def can_fall(self, pile_level: int, rock: list[int]) -> bool:
  74.         for r in range(len(rock)):
  75.             if ((pile_level + r >= len(self.pile) - 1) or
  76.                     (rock[r] & self.pile[pile_level + r + 1])):
  77.                 return False
  78.         return True
  79.  
  80.     def drop_next(self) -> None:
  81.         rock = [int(i, 2) for i in self.rock_shapes[next(self.rock_shape)]]
  82.         self.pile = [0] * (3 + len(rock)) + self.pile
  83.         for pile_level in range(len(self.pile)):
  84.             direction = next(self.wind_direction)
  85.  
  86.             if self.can_shift(direction, pile_level, rock):
  87.                 for r in range(len(rock)):
  88.                     rock[r] = rock[r] >> 1 if direction == ">" else rock[r] << 1
  89.  
  90.             if not self.can_fall(pile_level, rock):
  91.                 for r in range(len(rock)):
  92.                     self.pile[pile_level + r] = self.pile[pile_level + r] | rock[r]
  93.                 while self.pile[0] == 0:
  94.                     self.pile.pop(0)
  95.                 return
  96.  
  97.     def dump(self) -> str:
  98.         output = ""
  99.         for r in self.pile:
  100.             output += "|" + \
  101.                       "".join(["#" if c == "1" else "." for c in format(r, "07b")]) + \
  102.                       "|\n"
  103.         output += "+-------+"
  104.         return output
  105.  
  106.  
  107. def find_pattern(data: list[int]) -> tuple[list[int], list[int]]:
  108.     for p in range(len(data)):
  109.         sd = data[p:]
  110.         for r in range(2, len(sd) // 2):
  111.             if sd[0:r] == sd[r:2 * r]:
  112.                 if all([(sd[0:r] == sd[y:y + r]) for y in range(r, len(sd) - r, r)]):
  113.                     return data[:p], data[p:p + r]
  114.     return [], []
  115.  
  116.  
  117. def part_1() -> int:
  118.     pile = RockPile(next(get_daily_input(DAY)))
  119.     for _ in range(2022):
  120.         pile.drop_next()
  121.     return pile.height
  122.  
  123.  
  124. def part_2() -> int:
  125.     num_rocks = 1000000000000
  126.     sample_size = 10000
  127.  
  128.     pile = RockPile(next(get_daily_input(DAY)))
  129.  
  130.     height_deltas = []
  131.     for _ in range(sample_size):
  132.         prev_height = pile.height
  133.         pile.drop_next()
  134.         height_deltas.append(pile.height - prev_height)
  135.  
  136.     preamble, repetition = find_pattern(height_deltas)
  137.     p_len = len(preamble)
  138.     r_len = len(repetition)
  139.  
  140.     return sum(preamble) \
  141.         + sum(repetition) * ((num_rocks - p_len) // r_len) \
  142.         + sum(repetition[:((num_rocks - p_len) % r_len)])
  143.  
  144.  
  145. def main():
  146.     print(f"Part 1: {part_1()}")
  147.     print(f"Part 2: {part_2()}")
  148.  
  149.  
  150. if __name__ == "__main__":
  151.     main()
  152.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement