refactor: extract region logic into separate package

This commit is contained in:
Stevan Freeborn
2025-12-25 13:44:36 -06:00
parent 065990860d
commit ecc2d37c8f
2 changed files with 162 additions and 118 deletions
+4 -118
View File
@@ -2,10 +2,9 @@ package main
import ( import (
"regexp" "regexp"
"sort"
"strconv"
"strings" "strings"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/region"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape" "github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape"
"github.com/StevanFreeborn/advent-of-code-2025/internal/file" "github.com/StevanFreeborn/advent-of-code-2025/internal/file"
) )
@@ -22,7 +21,7 @@ func SolvePartOne(filePath string) int {
total := 0 total := 0
shapes := map[int][]shape.Shape{} shapes := map[int][]shape.Shape{}
regions := []string{} regions := []region.Region{}
for _, section := range sections { for _, section := range sections {
lines := newLineRegex.Split(strings.TrimSpace(section), -1) lines := newLineRegex.Split(strings.TrimSpace(section), -1)
@@ -36,129 +35,16 @@ func SolvePartOne(filePath string) int {
for _, line := range lines { for _, line := range lines {
if strings.Contains(line, COLON) { if strings.Contains(line, COLON) {
regions = append(regions, line) regions = append(regions, region.From(line))
} }
} }
} }
for _, region := range regions { for _, region := range regions {
parts := strings.Split(region, COLON) if region.CanFit(shapes) {
dims := strings.Split(parts[0], X)
width, _ := strconv.Atoi(dims[0])
height, _ := strconv.Atoi(dims[1])
countsStr := strings.Fields(parts[1])
requiredShapes := []int{}
for id, s := range countsStr {
count, _ := strconv.Atoi(s)
for range count {
requiredShapes = append(requiredShapes, id)
}
}
if canFit(width, height, requiredShapes, shapes) {
total++ total++
} }
} }
return total return total
} }
type item struct {
id int
area int
variants []shape.Shape
}
func canFit(width int, height int, requiredShapes []int, shapes map[int][]shape.Shape) bool {
totalArea := 0
itemsToPlace := make([]item, 0, len(requiredShapes))
for _, id := range requiredShapes {
shapeVariants := shapes[id]
area := shapeVariants[0].Area()
totalArea += area
itemsToPlace = append(itemsToPlace, item{id: id, area: area, variants: shapeVariants})
}
if totalArea > width*height {
return false
}
sort.Slice(itemsToPlace, func(i, j int) bool {
return itemsToPlace[i].area > itemsToPlace[j].area
})
grid := make([][]bool, height)
for i := range grid {
grid[i] = make([]bool, width)
}
return checkFit(0, itemsToPlace, grid, width, height)
}
func checkFit(index int, itemsToPlace []item, grid [][]bool, width int, height int) bool {
if index == len(itemsToPlace) {
return true
}
itemToPlace := itemsToPlace[index]
for _, variant := range itemToPlace.variants {
maxRow := variant.MaxRow()
maxColumn := variant.MaxColumn()
lastRow := height - maxRow
lastColumn := width - maxColumn
for r := range lastRow {
for c := range lastColumn {
if canPlace(grid, r, c, variant) {
place(grid, r, c, variant)
if checkFit(index+1, itemsToPlace, grid, width, height) {
return true
}
unplace(grid, r, c, variant)
}
}
}
}
return false
}
func canPlace(grid [][]bool, r, c int, variant shape.Shape) bool {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
isOccupied := grid[absoluteRow][absoluteColumn]
if isOccupied {
return false
}
}
return true
}
func place(grid [][]bool, r, c int, variant shape.Shape) {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
grid[absoluteRow][absoluteColumn] = true
}
}
func unplace(grid [][]bool, r, c int, variant shape.Shape) {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
grid[absoluteRow][absoluteColumn] = false
}
}
+158
View File
@@ -0,0 +1,158 @@
// Package region provides a model and methods for manipulating a region
package region
import (
"sort"
"strconv"
"strings"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape"
)
type Region interface {
CanFit(shapes map[int][]shape.Shape) bool
}
type region struct {
width int
height int
requiredShapes []int
}
func From(line string) Region {
parts := strings.Split(line, ":")
dims := strings.Split(parts[0], "x")
width, _ := strconv.Atoi(dims[0])
height, _ := strconv.Atoi(dims[1])
countsStr := strings.Fields(parts[1])
requiredShapes := []int{}
for id, s := range countsStr {
count, _ := strconv.Atoi(s)
for range count {
requiredShapes = append(requiredShapes, id)
}
}
return region{
width: width,
height: height,
requiredShapes: requiredShapes,
}
}
func (r region) CanFit(shapes map[int][]shape.Shape) bool {
totalArea := 0
itemsToPlace := r.createItems(shapes)
for _, item := range itemsToPlace {
totalArea += item.area
}
if totalArea > r.area() {
return false
}
return r.checkFit(0, itemsToPlace, r.createGrid())
}
type item struct {
id int
area int
variants []shape.Shape
}
func (r region) checkFit(index int, itemsToPlace []item, grid [][]bool) bool {
if index == len(itemsToPlace) {
return true
}
currentItem := itemsToPlace[index]
for _, variant := range currentItem.variants {
maxRow := variant.MaxRow()
maxColumn := variant.MaxColumn()
lastRow := r.height - maxRow
lastColumn := r.width - maxColumn
for rowNumber := range lastRow {
for columnNumber := range lastColumn {
if canPlace(grid, rowNumber, columnNumber, variant) {
place(grid, rowNumber, columnNumber, variant)
if r.checkFit(index+1, itemsToPlace, grid) {
return true
}
unplace(grid, rowNumber, columnNumber, variant)
}
}
}
}
return false
}
func (r region) area() int {
return r.width * r.height
}
func (r region) createGrid() [][]bool {
grid := make([][]bool, r.height)
for i := range grid {
grid[i] = make([]bool, r.width)
}
return grid
}
func (r region) createItems(shapes map[int][]shape.Shape) []item {
itemsToPlace := make([]item, 0, len(r.requiredShapes))
for _, id := range r.requiredShapes {
shapeVariants := shapes[id]
area := shapeVariants[0].Area()
itemsToPlace = append(itemsToPlace, item{id: id, area: area, variants: shapeVariants})
}
sort.Slice(itemsToPlace, func(i, j int) bool {
return itemsToPlace[i].area > itemsToPlace[j].area
})
return itemsToPlace
}
func canPlace(grid [][]bool, r, c int, variant shape.Shape) bool {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
isOccupied := grid[absoluteRow][absoluteColumn]
if isOccupied {
return false
}
}
return true
}
func place(grid [][]bool, r, c int, variant shape.Shape) {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
grid[absoluteRow][absoluteColumn] = true
}
}
func unplace(grid [][]bool, r, c int, variant shape.Shape) {
for _, position := range variant.Positions() {
absoluteRow := r + position.Row()
absoluteColumn := c + position.Column()
grid[absoluteRow][absoluteColumn] = false
}
}