Day 8: Playground
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465


Python
Both of my solutions make use of a Heap and a Disjoint-Set-Union / Union-Find data structure
Part 1: Use a heap to keep 1000 shortest connections, then union them all in DSU and get the 3 largest connected components.
Part 2: Use a heap to keep all connections, then union the shortest ones until all components are connected.
click to view code
import heapq as hq from itertools import combinations from collections import Counter # Disjoint Set Union (or Union-Find) data structure # with path compression and union by rank class DSU: def __init__(self, size: int) -> None: self.parent = list(range(size)) # ith value is the parent node to node i self.rank = [1] * size # max depth of subtree rooted here (used for union by rank) def find(self, x: int) -> int: # if the node is not its own parent, we need to recurse on parent if x != self.parent[x]: # path compression self.parent[x] = self.find(self.parent[x]) return self.parent[x] def isConnected(self, x: int, y: int) -> bool: return self.find(x) == self.find(y) # returns a boolean whether or not union was needed def union(self, x: int, y: int) -> bool: rootX = self.find(x) rootY = self.find(y) if rootX == rootY: # no union needed return False if self.rank[rootX] > self.rank[rootY]: # rootX has deeper subtree, therefore set it as parent to rootY (and its subtree) self.parent[rootY] = rootX elif self.rank[rootX] < self.rank[rootY]: # rootY has deeper subtree, therefore set it as parent to rootX (and its subtree) self.parent[rootX] = rootY else: # both subtrees are of equal depth, therefore choose either one of them as the parent of the other # here we chose rootX as the parent of rootY, therefore rootX depth increases by 1 self.parent[rootY] = rootX self.rank[rootX] += 1 # union complete return True # parse puzzle input into junction coordinates (x, y, z) def parse_junctions(data: str): return [tuple(map(int, line.split(','))) for line in data.splitlines()] # get distance between two points in 3D space # skipping the sqrt because we only care about relative distances def get_dist(p1, p2): x1, y1, z1 = p1 x2, y2, z2 = p2 return (x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2 def part1(data: str, max_connections: int = 1000): junctions = parse_junctions(data) n = len(junctions) # <max_connections> shortest connections closest_connections = [] # iterate over all pairs of junctions # combinations is just some syntactic sugar to avoid a nested for loop (still O(n^2)) for i, j in combinations(range(n), 2): dist = get_dist(junctions[i], junctions[j]) # keep <max_connections> closest connections # have to use -dist to simulate max heap if len(closest_connections) < max_connections: hq.heappush(closest_connections, (-dist, i, j)) else: hq.heappushpop(closest_connections, (-dist, i, j)) # union all the closest connections dsu = DSU(n) for _, i, j in closest_connections: dsu.union(i, j) # count all circuit sizes circuit_sizes = Counter() for i in range(n): circuit_sizes[dsu.find(i)] += 1 # multiply the sizes of the 3 largest circuits ans = 1 for _, f in circuit_sizes.most_common(3): ans *= f return ans def part2(data: str): junctions = parse_junctions(data) n = len(junctions) # all n^2 junction connections all_connections = [] # combinations is just some syntactic sugar to avoid a nested for loop (still O(n^2)) for i, j in combinations(range(n), 2): dist = get_dist(junctions[i], junctions[j]) all_connections.append((dist, i, j)) # create min heap of all connections # Heapify later to benefit from the heapify algorithm (O(n) instead of O(n log n)) hq.heapify(all_connections) # connect junctions until all are connected dsu = DSU(n) unions = 0 while all_connections: _, i, j = hq.heappop(all_connections) unions += dsu.union(i, j) # if we have n-1 unions, that means all n junctions are connected if unions == n-1: return junctions[i][0] * junctions[j][0] assert False, "It's not possible that all junctions never connect" sample = """162,817,812 57,618,57 906,360,560 592,479,940 352,342,300 466,668,158 542,29,236 431,825,988 739,650,466 52,470,668 216,146,977 819,987,18 117,168,530 805,96,715 346,949,466 970,615,88 941,993,340 862,61,35 984,92,344 425,690,689""" assert part1(sample, max_connections=10) == 40 assert part2(sample) == 25272It seems like you forgot the backticks around the code. It’s very hard to read this way. Also python comments look like markdown headlines :]
Fixed, thanks!