Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 93 additions & 62 deletions src/day12/day12.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@ import (
"strings"
)

type point struct {
x, y int
type plot struct {
crop rune
top, bottom, left, right bool
regionId uint
}

type fence struct {
x, y int
direction rune
}

func (p point) surroundingFences() []fence {
top := fence{x: p.x, y: p.y, direction: '-'}
bottom := fence{x: p.x, y: p.y + 1, direction: '-'}
left := fence{x: p.x, y: p.y, direction: '|'}
right := fence{x: p.x + 1, y: p.y, direction: '|'}
return []fence{top, bottom, left, right}
type region struct {
crop rune
id uint
}

type patch struct {
Expand All @@ -27,79 +21,116 @@ type patch struct {
}

func Solve(input string) uint {
crops := parseInput(input)
garden := parseInput(input)
updateFences(&garden)
assignCropId(&garden)
mergeNeighbors(&garden)

var cost uint = 0
for _, crop := range crops {
uniquePatches, fencesUsed := findUniquePatches(crop)
details := patchDetails(uniquePatches, fencesUsed)
for _, d := range details {
cost += d.area * d.perimeter
}
for _, p := range getPatches(&garden) {
cost += p.area * p.perimeter
}
return cost
}

func parseInput(input string) map[rune][]point {
crops := make(map[rune][]point)
func parseInput(input string) [][]plot {
garden := make([][]plot, 0)
for j, line := range strings.Split(input, "\n") {
for i, plant := range line {
crops[plant] = append(crops[plant], point{x: i, y: j})
garden = append(garden, make([]plot, 0))
for _, r := range line {
garden[j] = append(garden[j], plot{crop: r})
}
}
return crops
return garden
}

func findUniquePatches(crop []point) (map[uint][]point, map[fence]uint) {
var nextPatchId uint = 1
patches := make(map[uint][]point)
fencesByPatchId := make(map[fence]uint)
for _, p := range crop {
usedNewPatchId := true
currentPatchId := nextPatchId
newlyUsedFences := make([]fence, 0)
func updateFences(garden *[][]plot) {
max := len((*garden)) - 1
for j, line := range *garden {
for i, plot := range line {
(*garden)[j][i].top = j == 0 || (*garden)[j-1][i].crop != plot.crop
(*garden)[j][i].bottom = j == max || (*garden)[j+1][i].crop != plot.crop
(*garden)[j][i].left = i == 0 || (*garden)[j][i-1].crop != plot.crop
(*garden)[j][i].right = i == max || (*garden)[j][i+1].crop != plot.crop
}
}
}

for _, f := range p.surroundingFences() {
if fencesByPatchId[f] == 0 {
newlyUsedFences = append(newlyUsedFences, f)
continue
func assignCropId(garden *[][]plot) {
nextRegionId := make(map[rune]uint)
for j, line := range *garden {
for i, plot := range line {
var plotId uint
if !plot.top {
plotId = (*garden)[j-1][i].regionId
} else if !plot.left {
plotId = (*garden)[j][i-1].regionId
} else {
plotId = nextRegionId[plot.crop] + 1
}
(*garden)[j][i].regionId = plotId

if fencesByPatchId[f] != currentPatchId {
patches[fencesByPatchId[f]] = append(patches[fencesByPatchId[f]], patches[currentPatchId]...)
delete(patches, currentPatchId)
currentPatchId = fencesByPatchId[f]
usedNewPatchId = false
if plot.right {
nextRegionId[plot.crop]++
}
delete(fencesByPatchId, f)
}
}
}

for _, f := range newlyUsedFences {
fencesByPatchId[f] = currentPatchId
}
func mergeNeighbors(garden *[][]plot) {
for {
mergesMade := false
mergedRegions := make(map[region]region)
for j, line := range *garden {
for i := 0; i < len(line)-1; i++ {
plot := line[i]
if plot.right || (*garden)[j][i+1].regionId == plot.regionId {
continue
}

patches[currentPatchId] = append(patches[currentPatchId], p)
var t, f uint
if plot.regionId > (*garden)[j][i+1].regionId {
t = plot.regionId
f = (*garden)[j][i+1].regionId
} else {
t = (*garden)[j][i+1].regionId
f = plot.regionId
}
to := region{crop: plot.crop, id: t}
from := region{crop: plot.crop, id: f}
mergedRegions[from] = to
mergesMade = true
}
}
if !mergesMade {
break
}

if usedNewPatchId {
nextPatchId++
for j, line := range *garden {
for i, plot := range line {
r := region{id: plot.regionId, crop: plot.crop}
if update, ok := mergedRegions[r]; ok {
(*garden)[j][i].regionId = update.id
}
}
}
}

return patches, fencesByPatchId
}

func patchDetails(patches map[uint][]point, fencesUsed map[fence]uint) []patch {
details := make([]patch, len(patches))
i := 0
for _, points := range patches {
details[i].area = uint(len(points))
for _, p := range points {
for _, f := range p.surroundingFences() {
if fencesUsed[f] > 0 {
details[i].perimeter++
func getPatches(garden *[][]plot) map[region]patch {
result := make(map[region]patch)
for _, line := range *garden {
for _, plot := range line {
r := region{id: plot.regionId, crop: plot.crop}
p := result[r]
p.area++
for _, f := range []bool{plot.top, plot.bottom, plot.left, plot.right} {
if f {
p.perimeter++
}
}
result[r] = p
}
i++
}
return details
return result
}
Loading
Loading