My Advent of Code solutions in Python. kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add solution for 2024/12

+136
+136
2024/day12.py
··· 1 + import fileinput 2 + from collections import defaultdict, deque 3 + 4 + from utils import Point, DIRS, N, S, E, W 5 + 6 + 7 + def process_plot(garden, start, visited): 8 + """ 9 + Walk through the garden using BFS from `start`, only 10 + visiting the same plant as present in `garden[start]`. 11 + 12 + Write seen locations back to `visited` so that we can 13 + keep track of which garden plots have already been seen. 14 + 15 + Returns the area, perimeter, and side count of the plot. 16 + """ 17 + plot = set() 18 + horizon = deque([start]) 19 + plant = garden.get(start) 20 + 21 + while horizon: 22 + node = horizon.popleft() 23 + if node in plot: 24 + continue 25 + 26 + plot.add(node) 27 + visited.add(node) 28 + 29 + for n in node.neighbours(): 30 + if n in garden and garden.get(n) == plant: 31 + horizon.append(n) 32 + 33 + # The area of the plot is simply how many locations we visited. 34 + area = len(plot) 35 + 36 + # Compute the perimeter by taking every plant location in the 37 + # plot, and counting how many neighbouring locations are *not* 38 + # also in the plot. Each of those locations contributes 1 unit 39 + # length to the overall perimeter of the plot. 40 + perimeter = sum(len(set(p.neighbours()) - plot) for p in plot) 41 + 42 + # Compute the number of sides. This one is a bit tricky. 43 + 44 + # Walk every row in the garden. For each position that is part 45 + # of this plot, look to see if there is a non-plot location above 46 + # and below it (similar to the perimeter calculation). Add this 47 + # to a set of "potential sides". We will need to eliminate extra 48 + # sides in a future step. 49 + row_sides = { 50 + N: defaultdict(set), 51 + S: defaultdict(set), 52 + } 53 + for y in range(-2, 150): 54 + for d in [N, S]: 55 + for x in range(-2, 150): 56 + p = Point(x, y) 57 + if p in plot and p + d not in plot: 58 + row_sides[d][y].add(p) 59 + 60 + # Do the same for every column in the garden, looking left/right. 61 + col_sides = { 62 + E: defaultdict(set), 63 + W: defaultdict(set) 64 + } 65 + 66 + for x in range(-2, 150): 67 + for d in [E, W]: 68 + for y in range(-2, 150): 69 + p = Point(x, y) 70 + if p in plot and p + d not in plot: 71 + col_sides[d][x].add(p) 72 + 73 + # Do another horizonal/vertical sweep of the garden. When we 74 + # "enter" a position that is part of a "side", increment the 75 + # side count. Continue to walk until we are not in that side 76 + # anymore. At this point, the polygon has turned or something, 77 + # or we might be passing over a "break" (imagine walking down 78 + # the horizontal legs of the letter "E" vertically). Don't start 79 + # counting another side until we reach an in-plot location again. 80 + sides = 0 81 + 82 + for d in [N, S]: 83 + for y in range(-2, 150): 84 + x = -2 85 + inside = False 86 + while x < 150: 87 + p = Point(x, y) 88 + if p in row_sides[d][y]: 89 + if not inside: 90 + sides += 1 91 + inside = True 92 + else: 93 + if inside: 94 + inside = False 95 + 96 + x += 1 97 + 98 + for d in [E, W]: 99 + for x in range(-2, 150): 100 + y = -2 101 + inside = False 102 + while y < 150: 103 + p = Point(x, y) 104 + if p in col_sides[d][x]: 105 + if not inside: 106 + sides += 1 107 + inside = True 108 + else: 109 + if inside: 110 + # print("exit inside") 111 + inside = False 112 + 113 + y += 1 114 + 115 + return area, perimeter, sides 116 + 117 + 118 + # Parse problem input. 119 + GARDEN = {} 120 + for y, line in enumerate(fileinput.input()): 121 + for x, c in enumerate(line.strip()): 122 + GARDEN[Point(x, y)] = c 123 + 124 + # Solve problem. 125 + part_1 = 0 126 + part_2 = 0 127 + visited = set() 128 + 129 + for pos in GARDEN: 130 + if pos not in visited: 131 + area, perim, sides = process_plot(GARDEN, pos, visited) 132 + part_1 += area * perim 133 + part_2 += area * sides 134 + 135 + print("Part 1:", part_1) 136 + print("Part 2:", part_2)