Guest User

AoC, Day 8

a guest
Dec 8th, 2025
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.00 KB | None | 0 0
  1. import itertools
  2. import math
  3. import time
  4.  
  5. def timer(func, *args, **kwargs):
  6.     def wrapper(*args, **kwargs):
  7.         a = time.time()
  8.         result = func(*args, **kwargs)
  9.         b = time.time()
  10.         print(func.__name__, (b - a))
  11.         return result
  12.    
  13.     return wrapper
  14.  
  15. def acquire_data(filename):
  16.     return open(filename).read().splitlines()
  17.  
  18. def calc_distance(x1, y1, z1, x2, y2, z2):
  19.     # While the actual formula calls for math.sqrt(), pow() is faster, and we don't really need the exact result.
  20.     return pow((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2, 2)
  21.  
  22. @timer
  23. def process_v1(data, max_attempts=1000, slice_len=3):
  24.     data = [eval(junction) for junction in data]
  25.     max_circuit_length = len(data)
  26.     distances, banned_junctions = {}, {}
  27.  
  28.     for junction_pair in itertools.product(data, repeat=2):
  29.         # Banning a junction means that it is no longer a valid option for junction_pair[1], which is useful.
  30.         # For instance, if the very first junction_pair[0] we encounter is (0, 0, 0), then two things happen.
  31.         # 1. We are about to encounter (0, 0, 0) paired with (0, 0, 0), which is not a valid pair. It will be immediately discarded.
  32.         # 2. Later on, once junction_pair[0] is (0, 0, 0) no longer, we'll be encountering (0, 0, 0) as junction_pair[1]. Yet, every
  33.         # such pair has already been processed: (x, y, z), (0, 0, 0) and (0, 0, 0), (x, y, z) are one and the same for our purposes.
  34.         banned_junctions[junction_pair[0]] = None
  35.         if junction_pair[1] in banned_junctions:
  36.             continue
  37.  
  38.         x1, y1, z1 = junction_pair[0]
  39.         x2, y2, z2 = junction_pair[1]
  40.        
  41.         # This is risky, because we can't be certain that all distances are unique, but it'll do, luckily.
  42.         distances[calc_distance(x1, y1, z1, x2, y2, z2)] = (junction_pair[0], junction_pair[1])
  43.  
  44.     circuitry = []
  45.     iterations = 0
  46.     last_connection = None
  47.  
  48.     for d in sorted(distances.items()):
  49.         _, junctions = d
  50.         iterations += 1
  51.        
  52.         if iterations > max_attempts > 0:
  53.             break
  54.  
  55.         newest_junction = None
  56.                            
  57.         for circuit in circuitry:
  58.             if junctions[0] in circuit and junctions[1] in circuit:
  59.                 break
  60.             elif junctions[0] in circuit:
  61.                 newest_junction = junctions[1]
  62.             elif junctions[1] in circuit:
  63.                 newest_junction = junctions[0]
  64.  
  65.             if newest_junction:
  66.                 circuit.add(newest_junction)
  67.                 break
  68.  
  69.         if not newest_junction:
  70.             # Reconstructing the list in this fashion beats appending in terms of performance (with the further actions in mind).
  71.             circuitry = [set([junctions[0], junctions[1]])] + circuitry
  72.  
  73.         # And now we have to check whether there are any pairs of circuits which have common elements, meaning they must be condensed into a single circuit.
  74.         diff = bool(newest_junction)
  75.         if diff:
  76.             # We know that a newly added junction is present in at least one circuit. No escaping that.
  77.             diff = len([c for c in circuitry if newest_junction in c])
  78.             # But we need it to be present in at least two circuits, otherwise we should just carry on.
  79.             diff = bool(diff > 1)
  80.  
  81.         circuitry = sorted(circuitry, key=len)
  82.  
  83.         # If a new circuit has just been added, then it's not a part of any other circuits.
  84.         # Otherwise one of them would have been modified, raising the newest_junction flag.
  85.         # Thus, the following loop is pointless, and the value of diff will let us skip it.
  86.         while diff:
  87.             b = time.time()
  88.             unified_circuit = None
  89.             diff = len(circuitry)
  90.  
  91.             for i, main_circuit in enumerate(circuitry):
  92.                 for j in range(i+1, len(circuitry)):
  93.                     if main_circuit.intersection(circuitry[j]):
  94.                         unified_circuit = (main_circuit.union(circuitry[j]))
  95.                         circuitry[i] = circuitry[j] = None
  96.                         circuitry.append(unified_circuit)
  97.                         break
  98.  
  99.                 if unified_circuit:
  100.                     circuitry = [c for c in circuitry if c]
  101.                     break
  102.            
  103.             diff -= len(circuitry)
  104.  
  105.         if not last_connection and max([len(c) for c in circuitry]) == max_circuit_length:
  106.             last_connection = d[1]
  107.  
  108.         if len(circuitry) == 1 and len(circuitry[0]) == max_circuit_length:
  109.             break
  110.  
  111.     circuitry_sizes = sorted([len(c) for c in circuitry], reverse=True)
  112.  
  113.     if last_connection:
  114.         last_connection = last_connection[0][0] * last_connection[1][0]
  115.    
  116.     return math.prod(circuitry_sizes[:slice_len]), last_connection
  117.            
  118. @timer
  119. def process_v2(data):
  120.     return process_v1(data, max_attempts=0)
  121.  
  122. @timer
  123. def main(func):
  124.     data = acquire_data("008 - Input.txt")
  125.     return func(data)
  126.  
  127. print(main(process_v1))
  128. print(main(process_v2))
Advertisement
Add Comment
Please, Sign In to add comment