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) + } + } + }) }