From f99af72e2e733d4611ccb72c2cf9c64a2f6d0283 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Tue, 23 Dec 2025 08:18:01 -0600 Subject: [PATCH 1/2] feat: wip on day 12 part 1 --- cmd/12/main.go | 59 ++++++++++ cmd/12/main_test.go | 17 +++ cmd/12/shape/shape.go | 217 +++++++++++++++++++++++++++++++++++++ cmd/12/shape/shape_test.go | 9 ++ 4 files changed, 302 insertions(+) create mode 100644 cmd/12/main.go create mode 100644 cmd/12/main_test.go create mode 100644 cmd/12/shape/shape.go create mode 100644 cmd/12/shape/shape_test.go diff --git a/cmd/12/main.go b/cmd/12/main.go new file mode 100644 index 0000000..53ce3ff --- /dev/null +++ b/cmd/12/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape" + "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 X = "x" + +func SolvePartOne(filePath string) int { + input := file.ReadAllText(filePath) + sections := strings.Split(strings.TrimSpace(input), NEWLINE+NEWLINE) + + shapes := []shape.Shape{} + regions := []string{} + + for _, section := range sections { + lines := strings.Split(strings.TrimSpace(section), NEWLINE) + header := lines[0] + + if strings.Contains(header, COLON) && strings.Contains(header, X) == false { + shapes = append(shapes, shape.From(lines)) + continue + } + + for _, line := range lines { + if strings.Contains(line, COLON) { + regions = append(regions, line) + } + } + } + + // TODO: We are currently not generating + // all the expected variants. Need to debug this + // WRITE AUTOMATED UNIT TESTS DUMMY + // WHEN YOU READ THIS IN THE FUTURE YOU ARE GOING + // TO WANT TO IGNORE IT...DON'T! + // - Past Stevan + for _, s := range shapes { + if s.Id() != 1 && s.Id() != 2 && s.Id() != 0 { + continue + } + + fmt.Println("SHAPE ID", s.Id()) + + for _, v := range s.GenerateVariants() { + fmt.Println(v) + } + } + + return 0 +} diff --git a/cmd/12/main_test.go b/cmd/12/main_test.go new file mode 100644 index 0000000..ffe9186 --- /dev/null +++ b/cmd/12/main_test.go @@ -0,0 +1,17 @@ +package main_test + +import ( + "testing" + + solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/12" +) + +func TestSolvePartOne(t *testing.T) { + expected := -1 + + result := solution.SolvePartOne("EXAMPLE.txt") + + if result != expected { + t.Errorf("got %d but wanted %d", result, expected) + } +} diff --git a/cmd/12/shape/shape.go b/cmd/12/shape/shape.go new file mode 100644 index 0000000..d4173e4 --- /dev/null +++ b/cmd/12/shape/shape.go @@ -0,0 +1,217 @@ +// 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" +) + +// shape.GenerateAllVariants() []Shape + +type Shape interface { + Id() int + GetKey() string + GenerateVariants() []Shape + RotateClockwise() Shape + FlipVertically() Shape +} + +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) GenerateVariants() []Shape { + shapes := []Shape{} + var current Shape + current = s + 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 { + 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() + } + + 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() +} diff --git a/cmd/12/shape/shape_test.go b/cmd/12/shape/shape_test.go new file mode 100644 index 0000000..8ebf5d0 --- /dev/null +++ b/cmd/12/shape/shape_test.go @@ -0,0 +1,9 @@ +package shape_test + +import ( + "testing" +) + +func TestFrom(t *testing.T) { + +} From 52b76f4ce5806f54e3dc52aa406f30d5b504d9d7 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:35:14 -0600 Subject: [PATCH 2/2] feat: solve day 12 and there is no part 2 --- README.md | 2 +- cmd/12/main.go | 151 +++++++++++++++++++++++++++++++------ cmd/12/main_test.go | 15 +++- cmd/12/shape/shape.go | 89 ++++++++++++++++------ cmd/12/shape/shape_test.go | 142 +++++++++++++++++++++++++++++++++- 5 files changed, 351 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 8fcdb1c..6aa92b3 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/cmd/12/main.go b/cmd/12/main.go index 53ce3ff..347f280 100644 --- a/cmd/12/main.go +++ b/cmd/12/main.go @@ -1,32 +1,36 @@ package main import ( - "fmt" + "regexp" + "sort" + "strconv" "strings" "github.com/StevanFreeborn/advent-of-code-2025/cmd/12/shape" "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 X = "x" func SolvePartOne(filePath string) int { - input := file.ReadAllText(filePath) - sections := strings.Split(strings.TrimSpace(input), NEWLINE+NEWLINE) + twoNewLineRegex := regexp.MustCompile(`\r?\n\r?\n`) + 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{} for _, section := range sections { - lines := strings.Split(strings.TrimSpace(section), NEWLINE) + lines := newLineRegex.Split(strings.TrimSpace(section), -1) header := lines[0] 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 } @@ -37,23 +41,124 @@ func SolvePartOne(filePath string) int { } } - // TODO: We are currently not generating - // all the expected variants. Need to debug this - // WRITE AUTOMATED UNIT TESTS DUMMY - // WHEN YOU READ THIS IN THE FUTURE YOU ARE GOING - // TO WANT TO IGNORE IT...DON'T! - // - Past Stevan - for _, s := range shapes { - if s.Id() != 1 && s.Id() != 2 && s.Id() != 0 { - continue + 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) + } } - fmt.Println("SHAPE ID", s.Id()) - - for _, v := range s.GenerateVariants() { - fmt.Println(v) + if canFit(width, height, requiredShapes, shapes) { + total++ } } - 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 + } } diff --git a/cmd/12/main_test.go b/cmd/12/main_test.go index ffe9186..750a22a 100644 --- a/cmd/12/main_test.go +++ b/cmd/12/main_test.go @@ -6,8 +6,9 @@ import ( solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/12" ) -func TestSolvePartOne(t *testing.T) { - expected := -1 +// NOTE: This takes long time +func TestSolvePartOneWithExampleInput(t *testing.T) { + expected := 2 result := solution.SolvePartOne("EXAMPLE.txt") @@ -15,3 +16,13 @@ func TestSolvePartOne(t *testing.T) { 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) + } +} diff --git a/cmd/12/shape/shape.go b/cmd/12/shape/shape.go index d4173e4..1a852dd 100644 --- a/cmd/12/shape/shape.go +++ b/cmd/12/shape/shape.go @@ -11,14 +11,17 @@ import ( "github.com/StevanFreeborn/advent-of-code-2025/internal/position" ) -// shape.GenerateAllVariants() []Shape - 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 { @@ -46,37 +49,81 @@ func From(lines []string) Shape { 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 - // 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 = current.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() - } + // // 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{} diff --git a/cmd/12/shape/shape_test.go b/cmd/12/shape/shape_test.go index 8ebf5d0..722110a 100644 --- a/cmd/12/shape/shape_test.go +++ b/cmd/12/shape/shape_test.go @@ -1,9 +1,149 @@ package shape_test import ( + "strings" "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) + } + } + }) }