Advertisement
JonathanGupton

Advent of Code 2022 - Day 16

Dec 16th, 2022
340
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.94 KB | None | 0 0
  1. from collections import defaultdict
  2. from functools import cache
  3.  
  4. import networkx as nx
  5.  
  6.  
  7. CapacityMap = dict[str, int]
  8.  
  9.  
  10. def parse(filepath) -> tuple[nx.Graph, CapacityMap]:
  11.     G = nx.Graph()
  12.     capacity: dict[str, int] = defaultdict(int)
  13.     with open(filepath, "r") as f:
  14.         for line in f.readlines():
  15.             left, right = line.split(";")
  16.             name = left[6:8].strip()
  17.             node_capacity = int(left[23:])
  18.             capacity[name] = node_capacity
  19.             for child in (node.strip() for node in right[23:].split(",")):
  20.                 G.add_edge(name, child)
  21.     return G, capacity
  22.  
  23.  
  24. def find_max_open_valve_path(g: nx.Graph, capacities: CapacityMap, steps=30):
  25.     @cache
  26.     def distance(start, end):
  27.         return nx.shortest_path(g, start, end)
  28.  
  29.     @cache
  30.     def _search(current, closed_nodes, open_nodes, steps) -> int:
  31.         if not closed_nodes:
  32.             return 0
  33.  
  34.         max_value = 0
  35.         for dest_node in closed_nodes:
  36.             path = distance(current, dest_node)
  37.             if len(path) < steps:
  38.                 flow_lifetime = steps - len(path)
  39.                 amortized_value = flow_lifetime * capacities[dest_node]
  40.                 next_closed_nodes = frozenset(
  41.                     node for node in closed_nodes if node != dest_node
  42.                 )
  43.                 next_opened_nodes = frozenset([*open_nodes, dest_node])
  44.                 max_value = max(
  45.                     max_value,
  46.                     amortized_value
  47.                     + _search(
  48.                         dest_node, next_closed_nodes, next_opened_nodes, flow_lifetime
  49.                     ),
  50.                 )
  51.         return max_value
  52.  
  53.     open_nodes = frozenset(["AA"])
  54.     closed_with_capacity = frozenset(k for (k, v) in capacities.items() if v)
  55.     start = "AA"
  56.     steps = 30
  57.     return _search(start, closed_with_capacity, open_nodes, steps)
  58.  
  59.  
  60. def find_max_open_valve_path_elephant(g: nx.Graph, capacities: CapacityMap, steps=26):
  61.     @cache
  62.     def distance(start, end):
  63.         return nx.shortest_path(g, start, end)
  64.  
  65.     @cache
  66.     def _search(
  67.         h_position, e_position, closed_nodes, steps, h_residual, e_residual
  68.     ) -> int:
  69.         if not closed_nodes:
  70.             return 0
  71.  
  72.         max_value = 0
  73.  
  74.         if (h_residual == e_residual) or (h_residual < e_residual):
  75.             start_node = h_position
  76.             current_residual = h_residual
  77.         else:
  78.             start_node = e_position
  79.             current_residual = e_residual
  80.  
  81.         for dest_node in closed_nodes:
  82.             path = distance(start_node, dest_node)
  83.             if len(path) < (steps - current_residual):
  84.                 flow_lifetime = steps - len(path) - current_residual
  85.                 amortized_value = flow_lifetime * capacities[dest_node]
  86.                 next_closed_nodes = frozenset(
  87.                     node for node in closed_nodes if node != dest_node
  88.                 )
  89.  
  90.                 if (h_residual == e_residual) or (h_residual < e_residual):
  91.                     next_e_residual = e_residual - len(path)
  92.                     next_h_residual = h_residual + len(path)
  93.                     next_h_position = dest_node
  94.                     next_e_position = e_position
  95.  
  96.                     if next_h_residual < next_e_residual:
  97.                         next_steps = steps
  98.                     else:
  99.                         next_steps = steps - next_h_residual
  100.  
  101.                 else:
  102.                     next_h_residual = h_residual - len(path)
  103.                     next_e_residual = e_residual + len(path)
  104.                     next_e_position = dest_node
  105.                     next_h_position = h_position
  106.                     if next_e_residual < next_h_residual:
  107.                         next_steps = steps
  108.                     else:
  109.                         next_steps = steps - next_e_residual
  110.  
  111.                 max_value = max(
  112.                     max_value,
  113.                     amortized_value
  114.                     + _search(
  115.                         next_h_position,
  116.                         next_e_position,
  117.                         next_closed_nodes,
  118.                         next_steps,
  119.                         next_h_residual,
  120.                         next_e_residual,
  121.                     ),
  122.                 )
  123.         return max_value
  124.  
  125.     init_open_nodes = frozenset(["AA"])
  126.     closed_with_capacity = frozenset(k for (k, v) in capacities.items() if v)
  127.     init_e_position = "AA"
  128.     init_h_position = "AA"
  129.     init_h_residual = 0
  130.     init_e_residual = 0
  131.     return _search(
  132.         init_h_position,
  133.         init_e_position,
  134.         closed_with_capacity,
  135.         steps,
  136.         init_h_residual,
  137.         init_e_residual,
  138.     )
  139.  
  140.  
  141. def part_one(filepath) -> int:
  142.     g, capacity = parse(filepath)
  143.     return find_max_open_valve_path(g, capacity, 30)
  144.  
  145.  
  146. def part_two(filepath) -> int:
  147.     g, capacity = parse(filepath)
  148.     return find_max_open_valve_path_elephant(g, capacity, 26)
  149.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement