Day 12: Garden Groups
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
You are viewing a single thread.
View all comments 2 points
*
Python
Part 1: Simple DFS counting up the cells and exposed edges
Part 2: Still DFS, however I chose to keep track of all segments of the area, merging them if two segments connected. In the end, number of non-overlapping, non-intersecting segments is equal to number of sides. Not the most efficient solution, but it works.
import os
from collections import defaultdict
# paths
here = os.path.dirname(os.path.abspath(__file__))
filepath = os.path.join(here, "input.txt")
# read input
with open(filepath, mode="r", encoding="utf8") as f:
data = f.read()
# setup input vars
garden = data.splitlines()
m, n = len(garden), len(garden[0])
def part1():
visited = set()
def calcFenceCostFrom(i, j):
"""Calculates the fencing cost of the region starting from coords (i, j)"""
global garden, m, n
plant_type = garden[i][j]
stack = [(i, j)]
area, perimeter = 0, 0
while stack:
ci, cj = stack.pop()
if (ci, cj) in visited:
continue
visited.add((ci, cj))
# add cell to area
area += 1
# check top cell
if ci > 0 and garden[ci - 1][cj] == plant_type:
stack.append((ci - 1, cj))
else:
# if no top cell, add the edge to perimeter
perimeter += 1
# check left cell
if cj > 0 and garden[ci][cj - 1] == plant_type:
stack.append((ci, cj - 1))
else:
# if no left cell, add the edge to perimeter
perimeter += 1
# check bottom cell
if ci < m - 1 and garden[ci + 1][cj] == plant_type:
stack.append((ci + 1, cj))
else:
# if no bottom cell, add the edge to perimeter
perimeter += 1
# check right cell
if cj < n - 1 and garden[ci][cj + 1] == plant_type:
stack.append((ci, cj + 1))
else:
# if no right cell, add the edge to perimeter
perimeter += 1
return area * perimeter
# calculate fencing cost for every region
fencing_cost = 0
for i in range(m):
for j in range(n):
if (i, j) in visited:
continue
fencing_cost += calcFenceCostFrom(i, j)
print(fencing_cost)
def part2():
visited = set()
def calcFenceCostFrom(i, j):
"""Calculates the fencing cost of the region starting from coords (i, j)"""
global garden, m, n
plant_type = garden[i][j]
stack = [(i, j)]
area = 0
# keep track of all distinct, non-intersecting horizontal and vertical segments
segments = {
"H": defaultdict(set),
"V": defaultdict(set)
} # fmt: skip
while stack:
ci, cj = stack.pop()
if (ci, cj) in visited:
continue
visited.add((ci, cj))
# add cell to area
area += 1
# check top cell
if ci > 0 and garden[ci - 1][cj] == plant_type:
stack.append((ci - 1, cj))
else:
# record edge segment
ei = ci - 0.25 # push out the horizontal segment
segment_set = segments["H"][ei]
ej_from, ej_to = cj - 0.5, cj + 0.5 # extend the segment to connect with neighbors
merge_segments(segment_set, ej_from, ej_to) # merge with current segment set
# check left cell
if cj > 0 and garden[ci][cj - 1] == plant_type:
stack.append((ci, cj - 1))
else:
# record edge segment
ej = cj - 0.25 # push out the vertical segment
segment_set = segments["V"][ej]
ei_from, ei_to = ci - 0.5, ci + 0.5 # extend the segment to connect with neighbors
merge_segments(segment_set, ei_from, ei_to) # merge with current segment set
# check bottom cell
if ci < m - 1 and garden[ci + 1][cj] == plant_type:
stack.append((ci + 1, cj))
else:
# record edge segment
ei = ci + 0.25 # push out the horizontal segment
segment_set = segments["H"][ei]
ej_from, ej_to = cj - 0.5, cj + 0.5 # extend the segment to connect with neighbors
merge_segments(segment_set, ej_from, ej_to) # merge with current segment set
# check right cell
if cj < n - 1 and garden[ci][cj + 1] == plant_type:
stack.append((ci, cj + 1))
else:
# record edge segment
ej = cj + 0.25 # push out the vertical segment
segment_set = segments["V"][ej]
ei_from, ei_to = ci - 0.5, ci + 0.5 # extend the segment to connect with neighbors
merge_segments(segment_set, ei_from, ei_to) # merge with current segment set
# number of distinct segments == number of sides
sides = sum(len(segment_set) for seg_dict in segments.values() for segment_set in seg_dict.values())
return area * sides
def merge_segments(segment_set: set, idx_from: int, idx_to: int):
"""Merge segment into existing segment set"""
# find any overlapping / intersecting segments before and after current
former_segment, latter_segment = None, None
for s_from, s_to in segment_set:
if s_from < idx_from and s_to >= idx_from:
former_segment = (s_from, s_to)
if s_to > idx_to and s_from <= idx_to:
latter_segment = (s_from, s_to)
if former_segment is None and latter_segment is None:
# there is no overlapping segment
segment_set.add((idx_from, idx_to))
elif former_segment == latter_segment:
# the overlap segment contains our full segment
pass
elif former_segment is None:
# there is a latter segment only
segment_set.remove(latter_segment)
segment_set.add((idx_from, latter_segment[1]))
elif latter_segment is None:
# there is a former segment only
segment_set.remove(former_segment)
segment_set.add((former_segment[0], idx_to))
else:
# both are disconnected segments
segment_set.remove(former_segment)
segment_set.remove(latter_segment)
segment_set.add((former_segment[0], latter_segment[1]))
fencing_cost = 0
for i in range(m):
for j in range(n):
if (i, j) in visited:
continue
fencing_cost += calcFenceCostFrom(i, j)
print(fencing_cost)
part1()
part2()