@@ -4,21 +4,15 @@ import (
44 "strings"
55)
66
7- type point struct {
8- x , y int
7+ type plot struct {
8+ crop rune
9+ top , bottom , left , right bool
10+ regionId uint
911}
1012
11- type fence struct {
12- x , y int
13- direction rune
14- }
15-
16- func (p point ) surroundingFences () []fence {
17- top := fence {x : p .x , y : p .y , direction : '-' }
18- bottom := fence {x : p .x , y : p .y + 1 , direction : '-' }
19- left := fence {x : p .x , y : p .y , direction : '|' }
20- right := fence {x : p .x + 1 , y : p .y , direction : '|' }
21- return []fence {top , bottom , left , right }
13+ type region struct {
14+ crop rune
15+ id uint
2216}
2317
2418type patch struct {
@@ -27,79 +21,116 @@ type patch struct {
2721}
2822
2923func Solve (input string ) uint {
30- crops := parseInput (input )
24+ garden := parseInput (input )
25+ updateFences (& garden )
26+ assignCropId (& garden )
27+ mergeNeighbors (& garden )
28+
3129 var cost uint = 0
32- for _ , crop := range crops {
33- uniquePatches , fencesUsed := findUniquePatches (crop )
34- details := patchDetails (uniquePatches , fencesUsed )
35- for _ , d := range details {
36- cost += d .area * d .perimeter
37- }
30+ for _ , p := range getPatches (& garden ) {
31+ cost += p .area * p .perimeter
3832 }
3933 return cost
4034}
4135
42- func parseInput (input string ) map [ rune ][]point {
43- crops := make (map [ rune ][]point )
36+ func parseInput (input string ) [ ][]plot {
37+ garden := make ([ ][]plot , 0 )
4438 for j , line := range strings .Split (input , "\n " ) {
45- for i , plant := range line {
46- crops [plant ] = append (crops [plant ], point {x : i , y : j })
39+ garden = append (garden , make ([]plot , 0 ))
40+ for _ , r := range line {
41+ garden [j ] = append (garden [j ], plot {crop : r })
4742 }
4843 }
49- return crops
44+ return garden
5045}
5146
52- func findUniquePatches (crop []point ) (map [uint ][]point , map [fence ]uint ) {
53- var nextPatchId uint = 1
54- patches := make (map [uint ][]point )
55- fencesByPatchId := make (map [fence ]uint )
56- for _ , p := range crop {
57- usedNewPatchId := true
58- currentPatchId := nextPatchId
59- newlyUsedFences := make ([]fence , 0 )
47+ func updateFences (garden * [][]plot ) {
48+ max := len ((* garden )) - 1
49+ for j , line := range * garden {
50+ for i , plot := range line {
51+ (* garden )[j ][i ].top = j == 0 || (* garden )[j - 1 ][i ].crop != plot .crop
52+ (* garden )[j ][i ].bottom = j == max || (* garden )[j + 1 ][i ].crop != plot .crop
53+ (* garden )[j ][i ].left = i == 0 || (* garden )[j ][i - 1 ].crop != plot .crop
54+ (* garden )[j ][i ].right = i == max || (* garden )[j ][i + 1 ].crop != plot .crop
55+ }
56+ }
57+ }
6058
61- for _ , f := range p .surroundingFences () {
62- if fencesByPatchId [f ] == 0 {
63- newlyUsedFences = append (newlyUsedFences , f )
64- continue
59+ func assignCropId (garden * [][]plot ) {
60+ nextRegionId := make (map [rune ]uint )
61+ for j , line := range * garden {
62+ for i , plot := range line {
63+ var plotId uint
64+ if ! plot .top {
65+ plotId = (* garden )[j - 1 ][i ].regionId
66+ } else if ! plot .left {
67+ plotId = (* garden )[j ][i - 1 ].regionId
68+ } else {
69+ plotId = nextRegionId [plot .crop ] + 1
6570 }
71+ (* garden )[j ][i ].regionId = plotId
6672
67- if fencesByPatchId [f ] != currentPatchId {
68- patches [fencesByPatchId [f ]] = append (patches [fencesByPatchId [f ]], patches [currentPatchId ]... )
69- delete (patches , currentPatchId )
70- currentPatchId = fencesByPatchId [f ]
71- usedNewPatchId = false
73+ if plot .right {
74+ nextRegionId [plot .crop ]++
7275 }
73- delete (fencesByPatchId , f )
7476 }
77+ }
78+ }
7579
76- for _ , f := range newlyUsedFences {
77- fencesByPatchId [f ] = currentPatchId
78- }
80+ func mergeNeighbors (garden * [][]plot ) {
81+ for {
82+ mergesMade := false
83+ mergedRegions := make (map [region ]region )
84+ for j , line := range * garden {
85+ for i := 0 ; i < len (line )- 1 ; i ++ {
86+ plot := line [i ]
87+ if plot .right || (* garden )[j ][i + 1 ].regionId == plot .regionId {
88+ continue
89+ }
7990
80- patches [currentPatchId ] = append (patches [currentPatchId ], p )
91+ var t , f uint
92+ if plot .regionId > (* garden )[j ][i + 1 ].regionId {
93+ t = plot .regionId
94+ f = (* garden )[j ][i + 1 ].regionId
95+ } else {
96+ t = (* garden )[j ][i + 1 ].regionId
97+ f = plot .regionId
98+ }
99+ to := region {crop : plot .crop , id : t }
100+ from := region {crop : plot .crop , id : f }
101+ mergedRegions [from ] = to
102+ mergesMade = true
103+ }
104+ }
105+ if ! mergesMade {
106+ break
107+ }
81108
82- if usedNewPatchId {
83- nextPatchId ++
109+ for j , line := range * garden {
110+ for i , plot := range line {
111+ r := region {id : plot .regionId , crop : plot .crop }
112+ if update , ok := mergedRegions [r ]; ok {
113+ (* garden )[j ][i ].regionId = update .id
114+ }
115+ }
84116 }
85117 }
86-
87- return patches , fencesByPatchId
88118}
89119
90- func patchDetails (patches map [uint ][]point , fencesUsed map [fence ]uint ) []patch {
91- details := make ([]patch , len (patches ))
92- i := 0
93- for _ , points := range patches {
94- details [i ].area = uint (len (points ))
95- for _ , p := range points {
96- for _ , f := range p .surroundingFences () {
97- if fencesUsed [f ] > 0 {
98- details [i ].perimeter ++
120+ func getPatches (garden * [][]plot ) map [region ]patch {
121+ result := make (map [region ]patch )
122+ for _ , line := range * garden {
123+ for _ , plot := range line {
124+ r := region {id : plot .regionId , crop : plot .crop }
125+ p := result [r ]
126+ p .area ++
127+ for _ , f := range []bool {plot .top , plot .bottom , plot .left , plot .right } {
128+ if f {
129+ p .perimeter ++
99130 }
100131 }
132+ result [r ] = p
101133 }
102- i ++
103134 }
104- return details
135+ return result
105136}
0 commit comments