Merge pull request #28 from StevanFreeborn/stevanfreeborn/feat/day-twelve-part-one
feat: day twelve solved
This commit is contained in:
@@ -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. |
|
||||
| 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. |
|
||||
| 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 |
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/internal/file"
|
||||
)
|
||||
|
||||
const COLON = ":"
|
||||
const X = "x"
|
||||
|
||||
func SolvePartOne(filePath string) int {
|
||||
twoNewLineRegex := regexp.MustCompile(`\r?\n\r?\n`)
|
||||
newLineRegex := regexp.MustCompile(`\r?\n`)
|
||||
|
||||
input := file.ReadAllText(filePath)
|
||||
sections := twoNewLineRegex.Split(strings.TrimSpace(input), -1)
|
||||
|
||||
total := 0
|
||||
shapes := map[int][]shape.Shape{}
|
||||
regions := []string{}
|
||||
|
||||
for _, section := range sections {
|
||||
lines := newLineRegex.Split(strings.TrimSpace(section), -1)
|
||||
header := lines[0]
|
||||
|
||||
if strings.Contains(header, COLON) && strings.Contains(header, X) == false {
|
||||
shape := shape.From(lines)
|
||||
shapes[shape.Id()] = shape.GenerateVariants()
|
||||
continue
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, COLON) {
|
||||
regions = append(regions, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, region := range regions {
|
||||
parts := strings.Split(region, COLON)
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/12"
|
||||
)
|
||||
|
||||
// NOTE: This takes long time
|
||||
func TestSolvePartOneWithExampleInput(t *testing.T) {
|
||||
expected := 2
|
||||
|
||||
result := solution.SolvePartOne("EXAMPLE.txt")
|
||||
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Package shape provides a model and methods for representing and manipulating shapes
|
||||
package shape
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/internal/position"
|
||||
)
|
||||
|
||||
type Shape interface {
|
||||
Id() int
|
||||
GetKey() string
|
||||
GenerateVariants() []Shape
|
||||
RotateClockwise() Shape
|
||||
FlipVertically() Shape
|
||||
String() string
|
||||
Area() int
|
||||
MaxRow() int
|
||||
MaxColumn() int
|
||||
Positions() []position.Position
|
||||
}
|
||||
|
||||
type shape struct {
|
||||
id int
|
||||
positions []position.Position
|
||||
}
|
||||
|
||||
func From(lines []string) Shape {
|
||||
idStr := strings.TrimSuffix(lines[0], ":")
|
||||
id, _ := strconv.Atoi(idStr)
|
||||
|
||||
positions := []position.Position{}
|
||||
|
||||
for r, line := range lines[1:] {
|
||||
for c, char := range line {
|
||||
if char != '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
position := position.From(r, c)
|
||||
positions = append(positions, position)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
shapes := []Shape{}
|
||||
var current Shape
|
||||
current = s
|
||||
numberOfTransformations := 4
|
||||
|
||||
for range numberOfTransformations {
|
||||
shapes = append(shapes, current)
|
||||
flipped := current.FlipVertically()
|
||||
shapes = append(shapes, flipped)
|
||||
current = current.RotateClockwise()
|
||||
}
|
||||
|
||||
// // Rotate 90 clockwise
|
||||
// for range numberOfTransformations {
|
||||
// current = current.RotateClockwise()
|
||||
// shapes = append(shapes, current)
|
||||
// }
|
||||
//
|
||||
// current = s
|
||||
//
|
||||
// // 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{}
|
||||
|
||||
for _, s := range shapes {
|
||||
seenShapes[s.GetKey()] = s
|
||||
}
|
||||
|
||||
return slices.Collect(maps.Values(seenShapes))
|
||||
}
|
||||
|
||||
func (s shape) RotateClockwise() Shape {
|
||||
rotatedPositions := []position.Position{}
|
||||
|
||||
for _, p := range s.positions {
|
||||
rotatedPosition := position.From(p.Column(), -p.Row())
|
||||
rotatedPositions = append(rotatedPositions, rotatedPosition)
|
||||
}
|
||||
|
||||
rotatedShape := shape{
|
||||
id: s.id,
|
||||
positions: rotatedPositions,
|
||||
}
|
||||
|
||||
return normalize(rotatedShape)
|
||||
}
|
||||
|
||||
func (s shape) FlipVertically() Shape {
|
||||
flippedPositions := []position.Position{}
|
||||
|
||||
for _, p := range s.positions {
|
||||
flippedPosition := position.From(p.Row(), -p.Column())
|
||||
flippedPositions = append(flippedPositions, flippedPosition)
|
||||
}
|
||||
|
||||
flippedShape := shape{
|
||||
id: s.id,
|
||||
positions: flippedPositions,
|
||||
}
|
||||
|
||||
return normalize(flippedShape)
|
||||
}
|
||||
|
||||
func (s shape) GetKey() string {
|
||||
var sb strings.Builder
|
||||
|
||||
for _, p := range s.positions {
|
||||
fmt.Fprintf(&sb, "%d,%d|", p.Row(), p.Column())
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (s shape) Id() int {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func normalize(s shape) Shape {
|
||||
if len(s.positions) == 0 {
|
||||
return shape{
|
||||
id: s.id,
|
||||
positions: s.positions,
|
||||
}
|
||||
}
|
||||
|
||||
minRow := s.positions[0].Row()
|
||||
minColumn := s.positions[0].Column()
|
||||
|
||||
for _, p := range s.positions {
|
||||
if p.Row() < minRow {
|
||||
minRow = p.Row()
|
||||
}
|
||||
|
||||
if p.Column() < minColumn {
|
||||
minColumn = p.Column()
|
||||
}
|
||||
}
|
||||
|
||||
nps := []position.Position{}
|
||||
|
||||
for _, p := range s.positions {
|
||||
nr := p.Row() - minRow
|
||||
nc := p.Column() - minColumn
|
||||
np := position.From(nr, nc)
|
||||
nps = append(nps, np)
|
||||
}
|
||||
|
||||
return shape{
|
||||
id: s.id,
|
||||
positions: nps,
|
||||
}
|
||||
}
|
||||
|
||||
func (s shape) String() string {
|
||||
if len(s.positions) == 0 {
|
||||
return "(EMPTY SHAPE)"
|
||||
}
|
||||
|
||||
maxRow := 0
|
||||
maxColumn := 0
|
||||
|
||||
for _, p := range s.positions {
|
||||
if p.Row() > maxRow {
|
||||
maxRow = p.Row()
|
||||
}
|
||||
|
||||
if p.Column() > maxColumn {
|
||||
maxColumn = p.Column()
|
||||
}
|
||||
}
|
||||
|
||||
tempGrid := make([][]string, maxRow+1)
|
||||
|
||||
for r := range tempGrid {
|
||||
tempGrid[r] = make([]string, maxColumn+1)
|
||||
|
||||
for c := range tempGrid[r] {
|
||||
tempGrid[r][c] = "."
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range s.positions {
|
||||
tempGrid[p.Row()][p.Column()] = "#"
|
||||
}
|
||||
|
||||
var shape strings.Builder
|
||||
|
||||
for _, row := range tempGrid {
|
||||
var rowString strings.Builder
|
||||
|
||||
for _, str := range row {
|
||||
rowString.WriteString(str)
|
||||
}
|
||||
|
||||
shape.WriteString(rowString.String() + "\n")
|
||||
}
|
||||
|
||||
return shape.String()
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package shape_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user