feat: solve day 12 and there is no part 2

This commit is contained in:
Stevan Freeborn
2025-12-24 08:35:14 -06:00
parent f99af72e2e
commit 52b76f4ce5
5 changed files with 351 additions and 48 deletions
+1 -1
View File
@@ -37,4 +37,4 @@ go test ./cmd/01
| 09 | [Problem](https://adventofcode.com/2025/day/9) | [Solution](./cmd/09/) | Part 1 was such a nice break from the madness of day 8. Basically I need to go build some video games. | | 09 | [Problem](https://adventofcode.com/2025/day/9) | [Solution](./cmd/09/) | Part 1 was such a nice break from the madness of day 8. Basically I need to go build some video games. |
| 10 | [Problem](https://adventofcode.com/2025/day/10) | [Solution](./cmd/10/) | Part 1 took me way to look cause I couldn't figure out how to generate all button press combinations. | | 10 | [Problem](https://adventofcode.com/2025/day/10) | [Solution](./cmd/10/) | Part 1 took me way to look cause I couldn't figure out how to generate all button press combinations. |
| 11 | [Problem](https://adventofcode.com/2025/day/11) | [Solution](./cmd/11/) | DFS to the rescue again. | | 11 | [Problem](https://adventofcode.com/2025/day/11) | [Solution](./cmd/11/) | DFS to the rescue again. |
| 12 | [Problem](https://adventofcode.com/2025/day/12) | [Solution](./cmd/12/) | | | 12 | [Problem](https://adventofcode.com/2025/day/12) | [Solution](./cmd/12/) | I did it...but the example input takes a long time to find solution |
+128 -23
View File
@@ -1,32 +1,36 @@
package main package main
import ( import (
"fmt" "regexp"
"sort"
"strconv"
"strings" "strings"
"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"
) )
// TODO: Make the splitting
// safe for LF and CRLF
const NEWLINE = "\r\n"
const COLON = ":" const COLON = ":"
const X = "x" const X = "x"
func SolvePartOne(filePath string) int { func SolvePartOne(filePath string) int {
input := file.ReadAllText(filePath) twoNewLineRegex := regexp.MustCompile(`\r?\n\r?\n`)
sections := strings.Split(strings.TrimSpace(input), NEWLINE+NEWLINE) newLineRegex := regexp.MustCompile(`\r?\n`)
shapes := []shape.Shape{} input := file.ReadAllText(filePath)
sections := twoNewLineRegex.Split(strings.TrimSpace(input), -1)
total := 0
shapes := map[int][]shape.Shape{}
regions := []string{} regions := []string{}
for _, section := range sections { for _, section := range sections {
lines := strings.Split(strings.TrimSpace(section), NEWLINE) lines := newLineRegex.Split(strings.TrimSpace(section), -1)
header := lines[0] header := lines[0]
if strings.Contains(header, COLON) && strings.Contains(header, X) == false { if strings.Contains(header, COLON) && strings.Contains(header, X) == false {
shapes = append(shapes, shape.From(lines)) shape := shape.From(lines)
shapes[shape.Id()] = shape.GenerateVariants()
continue continue
} }
@@ -37,23 +41,124 @@ func SolvePartOne(filePath string) int {
} }
} }
// TODO: We are currently not generating for _, region := range regions {
// all the expected variants. Need to debug this parts := strings.Split(region, COLON)
// WRITE AUTOMATED UNIT TESTS DUMMY dims := strings.Split(parts[0], X)
// WHEN YOU READ THIS IN THE FUTURE YOU ARE GOING width, _ := strconv.Atoi(dims[0])
// TO WANT TO IGNORE IT...DON'T! height, _ := strconv.Atoi(dims[1])
// - Past Stevan
for _, s := range shapes { countsStr := strings.Fields(parts[1])
if s.Id() != 1 && s.Id() != 2 && s.Id() != 0 { requiredShapes := []int{}
continue
for id, s := range countsStr {
count, _ := strconv.Atoi(s)
for range count {
requiredShapes = append(requiredShapes, id)
}
} }
fmt.Println("SHAPE ID", s.Id()) if canFit(width, height, requiredShapes, shapes) {
total++
for _, v := range s.GenerateVariants() {
fmt.Println(v)
} }
} }
return 0 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
}
} }
+13 -2
View File
@@ -6,8 +6,9 @@ import (
solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/12" solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/12"
) )
func TestSolvePartOne(t *testing.T) { // NOTE: This takes long time
expected := -1 func TestSolvePartOneWithExampleInput(t *testing.T) {
expected := 2
result := solution.SolvePartOne("EXAMPLE.txt") result := solution.SolvePartOne("EXAMPLE.txt")
@@ -15,3 +16,13 @@ func TestSolvePartOne(t *testing.T) {
t.Errorf("got %d but wanted %d", result, expected) t.Errorf("got %d but wanted %d", result, expected)
} }
} }
func TestSolvePartOneWithInput(t *testing.T) {
expected := 550
result := solution.SolvePartOne("INPUT.txt")
if result != expected {
t.Errorf("got %d but wanted %d", result, expected)
}
}
+68 -21
View File
@@ -11,14 +11,17 @@ import (
"github.com/StevanFreeborn/advent-of-code-2025/internal/position" "github.com/StevanFreeborn/advent-of-code-2025/internal/position"
) )
// shape.GenerateAllVariants() []Shape
type Shape interface { type Shape interface {
Id() int Id() int
GetKey() string GetKey() string
GenerateVariants() []Shape GenerateVariants() []Shape
RotateClockwise() Shape RotateClockwise() Shape
FlipVertically() Shape FlipVertically() Shape
String() string
Area() int
MaxRow() int
MaxColumn() int
Positions() []position.Position
} }
type shape struct { type shape struct {
@@ -46,37 +49,81 @@ func From(lines []string) Shape {
return shape{id: id, positions: positions} return shape{id: id, positions: positions}
} }
func (s shape) Positions() []position.Position {
return s.positions
}
func (s shape) MaxRow() int {
maxRow := 0
for _, p := range s.positions {
if p.Row() > maxRow {
maxRow = p.Row()
}
}
return maxRow
}
func (s shape) MaxColumn() int {
maxColumn := 0
for _, p := range s.positions {
if p.Column() > maxColumn {
maxColumn = p.Column()
}
}
return maxColumn
}
func (s shape) Area() int {
return len(s.positions)
}
func (s shape) GenerateVariants() []Shape { func (s shape) GenerateVariants() []Shape {
shapes := []Shape{} shapes := []Shape{}
var current Shape var current Shape
current = s current = s
numberOfTransformations := 4 numberOfTransformations := 4
// Rotate 90 clockwise
for range numberOfTransformations {
current = current.RotateClockwise()
shapes = append(shapes, current)
}
current = s
// Flip then rotate 90 clockwise
for range numberOfTransformations { for range numberOfTransformations {
shapes = append(shapes, current) shapes = append(shapes, current)
flipped := current.FlipVertically() flipped := current.FlipVertically()
shapes = append(shapes, flipped) shapes = append(shapes, flipped)
current = flipped.RotateClockwise() current = current.RotateClockwise()
} }
current = s // // Rotate 90 clockwise
// for range numberOfTransformations {
// Rotate 90 clockwise then flip // current = current.RotateClockwise()
for range numberOfTransformations { // shapes = append(shapes, current)
shapes = append(shapes, current) // }
rotated := current.RotateClockwise() //
shapes = append(shapes, rotated) // current = s
current = rotated.FlipVertically() //
} // // Flip then rotate 90 clockwise
// for range numberOfTransformations {
// shapes = append(shapes, current)
// flipped := current.FlipVertically()
// shapes = append(shapes, flipped)
// current = flipped.RotateClockwise()
// }
//
// current = s
//
// // Rotate 90 clockwise then flip
// for range numberOfTransformations {
// shapes = append(shapes, current)
// rotated := current.RotateClockwise()
// shapes = append(shapes, rotated)
// current = rotated.FlipVertically()
// }
//
// current = s
// rotated := current.RotateClockwise().RotateClockwise()
// flipped := rotated.FlipVertically()
// shapes = append(shapes, flipped)
seenShapes := map[string]Shape{} seenShapes := map[string]Shape{}
+141 -1
View File
@@ -1,9 +1,149 @@
package shape_test package shape_test
import ( import (
"strings"
"testing" "testing"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape"
) )
func TestFrom(t *testing.T) { func TestGenerateVariants(t *testing.T) {
t.Run("works for shape 0", func(t *testing.T) {
expectedVariants := [][]string{
{
"###",
"##.",
"##.",
},
{
"###",
".##",
".##",
},
{
"###",
"###",
"#..",
},
{
"###",
"###",
"..#",
},
{
".##",
".##",
"###",
},
{
"##.",
"##.",
"###",
},
}
lines := []string{
"0:",
"###",
"##.",
"##.",
}
shape := shape.From(lines)
result := shape.GenerateVariants()
for _, ev := range expectedVariants {
expected := strings.Join(ev, "\n")
found := false
for _, v := range result {
if strings.TrimSpace(v.String()) == expected {
found = true
}
}
if found == false {
t.Errorf("expected to find variant but did not:\n%v", expected)
}
}
})
t.Run("works for shape 1", func(t *testing.T) {
expectedVariants := [][]string{
{
"###",
"##.",
".##",
},
{
".##",
"###",
"#.#",
},
{
"##.",
".##",
"###",
},
{
"#.#",
"###",
"##.",
},
{
"###",
".##",
"##.",
},
{
"##.",
"###",
"#.#",
},
{
"##.",
"###",
"#.#",
},
{
".##",
"##.",
"###",
},
{
"#.#",
"###",
".##",
},
}
lines := []string{
"0:",
"###",
"##.",
".##",
}
shape := shape.From(lines)
result := shape.GenerateVariants()
for _, ev := range expectedVariants {
expected := strings.Join(ev, "\n")
found := false
for _, v := range result {
if strings.TrimSpace(v.String()) == expected {
found = true
}
}
if found == false {
t.Errorf("expected to find variant but did not:\n%v", expected)
}
}
})
} }