Advertisement
globmont

AoC 21-18

Dec 18th, 2021
1,403
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.69 KB | None | 0 0
  1. from abc import ABC, abstractmethod
  2. import itertools
  3.  
  4. class Number(ABC):
  5.     def __init__(self) -> None:
  6.         self.parent = None
  7.        
  8.     def _check_explosions(self, nesting_level: int):
  9.         """Should handle any explosions and return the current Node (or its replacement) and a boolean indicating if
  10.        the node (or any of its children) exploded
  11.  
  12.        Parameters
  13.        ----------
  14.        nesting_level : int
  15.            How deep the current node is in the tree
  16.  
  17.        Returns
  18.        -------
  19.        Number, bool
  20.            The current node (or its replacement) and whether the node or its children exploded.
  21.        """
  22.         return self, False
  23.    
  24.     @abstractmethod
  25.     def _check_splits(self):
  26.         """Should handle any splits and return the current Node (or its replacement) along with a boolean indicating if
  27.        the node or any of its children did split.
  28.        """
  29.         pass
  30.    
  31.     @abstractmethod
  32.     def magnitude(self):
  33.         pass
  34.    
  35.     def reduce(self):
  36.         pass
  37.    
  38. class SnailfishNumber(Number):
  39.     def __init__(self, raw: str) -> None:
  40.         super().__init__()
  41.         self.left = None
  42.         self.right = None
  43.        
  44.         # Parse raw number
  45.         self.raw = raw
  46.         if self.raw is not None:
  47.             self._parse_raw_number()
  48.        
  49.     def __repr__(self) -> str:
  50.         return f"[{self.left}, {self.right}]"
  51.        
  52.     def _split_pair(raw: str):
  53.         """Splits a string SnailfishNumber into the left and right children. Used for parsing the raw snailfish strings.
  54.  
  55.        Parameters
  56.        ----------
  57.        raw : str
  58.            The raw snailfish string
  59.  
  60.        Returns
  61.        -------
  62.        str, str
  63.            The left and right children in string form
  64.        """
  65.         bracket_count = 0
  66.         for idx, char in enumerate(raw):
  67.             if char == "[":
  68.                 bracket_count += 1
  69.             elif char == "]":
  70.                 bracket_count -= 1
  71.                
  72.             if (bracket_count == 1) and (idx != 0):
  73.                 # Found matching bracket to inner pair
  74.                 return raw[1:idx + 1], raw[idx + 2: -1]
  75.        
  76.        
  77.     def _parse_raw_number(self):
  78.         """Parses a raw string as a SnailfishNumber
  79.        """
  80.         # Get rid of brackets
  81.         left, right = SnailfishNumber._split_pair(self.raw)
  82.        
  83.         self.left = SnailfishNumber(left) if left[0] == "[" else HumanNumber(int(left))        
  84.         self.right = SnailfishNumber(right) if right[0] == "[" else HumanNumber(int(right))
  85.        
  86.         self.left.parent = self
  87.         self.right.parent = self
  88.  
  89.    
  90.     def add(self, other):
  91.         """Adds another SnailfishNumber to the current SnailfishNumber.
  92.  
  93.        Parameters
  94.        ----------
  95.        other : SnailfishNumber
  96.            The number to add.
  97.  
  98.        Returns
  99.        -------
  100.        SnailfishNumber
  101.            The reduced result of the snailfish addition.
  102.        """
  103.         sn = SnailfishNumber(None)
  104.         sn.left = self
  105.         sn.right = other
  106.        
  107.         self.parent = sn
  108.         other.parent = sn
  109.        
  110.         sn.reduce()
  111.        
  112.         return sn
  113.    
  114.     def reduce(self):
  115.         """Reduces the current node if any reductions need to be performed. This is an in-place operation.
  116.        """
  117.         did_explode = True
  118.         did_split = True
  119.        
  120.         while did_explode or did_split:
  121.             did_explode = False
  122.             did_split = False
  123.          
  124.             _, did_explode = self._check_explosions(0)
  125.            
  126.             if did_explode:
  127.                 continue
  128.            
  129.             _, did_split = self._check_splits()
  130.    
  131.     def magnitude(self):
  132.         """Calculates the magnitude of the current node
  133.  
  134.        Returns
  135.        -------
  136.        int
  137.            The node's magnitude
  138.        """
  139.         left_magnitude = self.left.magnitude()
  140.         right_magnitude = self.right.magnitude()
  141.        
  142.         return 3 * left_magnitude + 2 * right_magnitude
  143.        
  144.     def _check_splits(self):
  145.         """Determines and handles any splits that occur due to large HumanNumbers (>= 10)
  146.  
  147.        Returns
  148.        -------
  149.        Number, bool
  150.            The current node along with whether any of its children split
  151.        """
  152.         self.left, left_did_split = self.left._check_splits()
  153.         self.left.parent = self
  154.            
  155.         if left_did_split:
  156.             return self, True
  157.        
  158.         self.right, right_did_split =  self.right._check_splits()
  159.         self.right.parent = self
  160.                
  161.         return self, right_did_split
  162.    
  163.     def _find_deepest_directional_child(self, direction: str):
  164.         """Finds the deepest child in a given consistent direction from the current node.
  165.  
  166.        Parameters
  167.        ----------
  168.        direction : str
  169.            Which direction to search in.
  170.  
  171.        Returns
  172.        -------
  173.        HumanNumber
  174.            The deepest HumanNumber node to the direction of the current node.
  175.        """
  176.         assert direction in ("left", "right"), "Direction must either be 'left' or 'right'."
  177.        
  178.         current = self
  179.         while type(current) is SnailfishNumber:
  180.             current = getattr(current, direction)
  181.            
  182.         return current
  183.    
  184.     def _find_nearest_directional_leaf(self, direction: str):
  185.         """Finds the nearest leaf node either to the left or the right of the current node anywhere in the tree.
  186.  
  187.        Parameters
  188.        ----------
  189.        direction : str
  190.            Which direction to search in (either left or right)
  191.  
  192.        Returns
  193.        -------
  194.        HumanNumber
  195.            The HumanNumber node closest to the current node in the given direction
  196.        """
  197.         assert direction in ("left", "right"), "Direction must either be 'left' or 'right'."
  198.         anti_direction = "right" if direction == "left" else "left"
  199.         current = self
  200.         first_directional_child = None
  201.        
  202.         while current.parent is not None and first_directional_child is None:
  203.             directional_child = getattr(current.parent, direction)
  204.             if directional_child is current:
  205.                 current = current.parent
  206.             else:
  207.                 if type(directional_child) is HumanNumber:
  208.                     # Found it
  209.                     return directional_child
  210.                 else:
  211.                     first_directional_child = directional_child._find_deepest_directional_child(anti_direction)
  212.        
  213.         return first_directional_child
  214.        
  215.     def _check_explosions(self, nesting_level: int):
  216.         """Checks if the current node will explode and handles the explosion if so.
  217.  
  218.        Parameters
  219.        ----------
  220.        nesting_level : int
  221.            The current node's nesting level within the tree.
  222.  
  223.        Returns
  224.        -------
  225.        Number, bool
  226.            The current node (or its replacement) along with whether the node or its children exploded
  227.        """
  228.         if nesting_level < 4:
  229.             # Not nested deep enough so we check the children
  230.             new_left, left_did_explode = self.left._check_explosions(nesting_level + 1)
  231.             new_left.parent = self
  232.             self.left = new_left
  233.            
  234.             if left_did_explode:
  235.                 return self, True
  236.            
  237.             new_right, right_did_explode = self.right._check_explosions(nesting_level + 1)
  238.             new_right.parent = self
  239.             self.right = new_right
  240.            
  241.             return self, right_did_explode          
  242.            
  243.         else:
  244.             # Explosion
  245.             # Find first left-child
  246.             first_left_child = self._find_nearest_directional_leaf("left")
  247.             if first_left_child is not None:
  248.                 first_left_child.value += self.left.value
  249.                
  250.             # Find first right-child
  251.             first_right_child = self._find_nearest_directional_leaf("right")
  252.             if first_right_child is not None:
  253.                 first_right_child.value += self.right.value
  254.        
  255.             return HumanNumber(0), True
  256.        
  257. class HumanNumber(Number):
  258.     def __init__(self, value: int) -> None:
  259.         super().__init__()
  260.         self.value = int(value)
  261.        
  262.     def __repr__(self) -> str:
  263.         return f"{self.value}"
  264.    
  265.     def _check_splits(self):
  266.         """Handles a split of a HumanNumber
  267.  
  268.        Returns
  269.        -------
  270.        Number, bool
  271.            Either the current node or its split replacement, along with whether the node split
  272.        """
  273.         if self.value >= 10:
  274.             sn = SnailfishNumber(None)
  275.             sn.left = HumanNumber(self.value // 2)
  276.             sn.right = HumanNumber(self.value - self.value // 2)
  277.             sn.parent = self.parent
  278.             sn.left.parent = self
  279.             sn.right.parent = self
  280.            
  281.             return sn, True
  282.        
  283.         return self, False
  284.    
  285.     def magnitude(self):
  286.         return self.value
  287.  
  288. if __name__ == "__main__":
  289.     file = "inputs/2021/18/data.txt"
  290.  
  291.     with open(file, "r") as f:
  292.         raw_numbers = f.read().split("\n")
  293.        
  294.     snailfish_sum = SnailfishNumber(raw_numbers[0])
  295.     for raw_number in raw_numbers[1:]:
  296.         second_number = SnailfishNumber(raw_number)
  297.         snailfish_sum = snailfish_sum.add(second_number)
  298.    
  299.     print(f"Part 1: {snailfish_sum.magnitude():,d}")
  300.    
  301.     max_magnitude = -1
  302.     for pair in itertools.combinations(raw_numbers, 2):
  303.         a = SnailfishNumber(pair[0])
  304.         b = SnailfishNumber(pair[1])
  305.         max_magnitude = max(max_magnitude, a.add(b).magnitude())
  306.        
  307.     print(f"Part 2: {max_magnitude:,d}")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement