diff --git a/.gitignore b/.gitignore index 04561b3..851b157 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,3 @@ tmp # Ignore these to avoid leaking local replacements to the devkit -./go.mod -./go.sum - -go.mod -go.sum \ No newline at end of file diff --git a/year25/day12/day.go b/year25/day12/day.go new file mode 100644 index 0000000..a41cba8 --- /dev/null +++ b/year25/day12/day.go @@ -0,0 +1,149 @@ +package day12 + +import ( + "fmt" + "io" + "strconv" + "strings" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" + "git.bizdoc.ro/private/devkit.git/gg" +) + +func Part1(ctx aoc.Context) (int, error) { + input, err := parseInput(ctx) + if err != nil { + return 0, fmt.Errorf("day12: failed to parse input: %w", err) + } + + ares := make([]int, len(input.Shapes)) + for i, shape := range input.Shapes { + ares[i] = shape.Area() + } + + solution := 0 + for _, region := range input.Regions { + regionSize := region.Rows * region.Cols + sum := 0 + for i, count := range region.ShapeCounts { + sum += count * ares[i] + } + canFit := sum < regionSize + if canFit { + solution += 1 + } + } + ctx.Println(input.String()) + return solution, nil +} + +type Shape [3][3]byte + +func (s *Shape) Area() int { + sum := 0 + for i := range s { + for j := range s[i] { + if s[i][j] == '#' { + sum += 1 + } + } + } + return sum +} + +type Region struct { + Rows, Cols int + ShapeCounts []int +} + +func (r *Region) WriteTo(w io.Writer) (n int64, err error) { + out := gg.NewSafeWriter(w) + fmt.Fprintf(out, "%dx%d ", r.Rows, r.Cols) + for _, count := range r.ShapeCounts { + out.WriteString(strconv.Itoa(count) + " ") + } + out.WriteString("\n") + return out.BytesWritten(), out.Err() +} + +type Input struct { + Shapes []Shape + Regions []Region +} + +func parseInput(ctx aoc.Context) (input Input, err error) { + text, err := ctx.BodyString() + if err != nil { + return input, fmt.Errorf("day12: failed to read input file: %w", err) + } + parts := strings.Split(text, "\n\n") + regionPart := parts[len(parts)-1] + parts = parts[0 : len(parts)-1] + + input.Shapes = make([]Shape, len(parts)) + for _, part := range parts { + // 0: + // ### + // ##. + // ##. + lines := strings.Split(part, "\n") + id, err := strconv.Atoi(lines[0][:len(lines[0])-1]) + if err != nil { + return input, fmt.Errorf("day12: failed to parse shape id: %w", err) + } + + shape := input.Shapes[id] + for i := range shape { + for j := range shape[i] { + shape[i][j] = lines[1+i][j] + } + } + input.Shapes[id] = shape // shape is value object + } + + for _, regionStr := range strings.Split(regionPart, "\n") { + // 12x5: 1 0 1 0 3 2 + var region Region + parts := strings.Split(regionStr, ": ") + for _, idstr := range strings.Split(parts[1], " ") { + id, err := strconv.Atoi(idstr) + if err != nil { + return input, fmt.Errorf("day12: failed to parse region id: %w", err) + } + region.ShapeCounts = append(region.ShapeCounts, id) + } + + parts2 := strings.Split(parts[0], "x") + region.Rows, err = strconv.Atoi(parts2[0]) + if err != nil { + return input, fmt.Errorf("day12: failed to parse region rows: %w", err) + } + + region.Cols, err = strconv.Atoi(parts2[1]) + if err != nil { + return input, fmt.Errorf("day12: failed to parse region cols: %w", err) + } + input.Regions = append(input.Regions, region) + } + return input, nil +} + +func (i Input) String() string { + buf := strings.Builder{} + + for shapeid, shape := range i.Shapes { + fmt.Fprintln(&buf, shapeid) + for i := range shape { + for j := range shape[i] { + buf.WriteString(string(shape[i][j]) + " ") + } + buf.WriteString("\n") + } + buf.WriteString("\n") + } + + for _, region := range i.Regions { + region.WriteTo(&buf) + } + return buf.String() +} diff --git a/year25/day12/day_test.go b/year25/day12/day_test.go new file mode 100644 index 0000000..8d2cd6a --- /dev/null +++ b/year25/day12/day_test.go @@ -0,0 +1,18 @@ +package day12 + +import ( + "testing" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/tests" +) + +func TestPart1Example(t *testing.T) { + const want = 2 + tests.Test(t, "example.txt", tests.Handler(Part1), want) +} + +func TestPart1(t *testing.T) { + t.Helper() + const want = 510 + tests.Test(t, "input.txt", tests.Handler(Part1), want) +} diff --git a/year25/tests/utils_test.go b/year25/tests/utils_test.go deleted file mode 100644 index e3bbc7f..0000000 --- a/year25/tests/utils_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main_test - -import ( - "bytes" - "fmt" - "io" - "log" - "os" - "testing" - "time" - - "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" -) - -func getInput(name string) io.Reader { - name = fmt.Sprintf("./data/%s.txt", name) - input, err := os.ReadFile(name) - if err != nil { - panic(err) - } - return bytes.NewReader(input) -} - -func assert(t *testing.T, err error, a, b any) { - t.Helper() - - if err != nil { - t.Error(err) - } - want := fmt.Sprintf("%v", a) - got := fmt.Sprintf("%v", b) - if got != want { - err = fmt.Errorf("want %s, got %s", want, got) - t.Error(err) - } -} - -type TestCase struct { - Name string - File string - Want any - Handler func(ctx aoc.Context) (any, error) -} - -var StderrLogger = log.New(os.Stderr, "", 0) -var DiscardLogger = log.New(io.Discard, "", 0) - -func handler[T any](f func(ctx aoc.Context) (T, error)) func(ctx aoc.Context) (any, error) { - return func(c aoc.Context) (any, error) { - return f(c) - } -} - -func handler2[T any, K any](param T, f func(ctx aoc.Context, param T) (K, error)) func(ctx aoc.Context) (any, error) { - return func(c aoc.Context) (any, error) { - return f(c, param) - } -} - -func runTestCase(t *testing.T, cases []TestCase, l *log.Logger) { - - t.Helper() - var totalTime time.Duration - defer func() { - t.Log("Total time:", totalTime.String()) - }() - for _, test := range cases { - t.Run(test.Name, func(t *testing.T) { - start := time.Now() - defer func() { - end := time.Since(start) - t.Log("elapsed:", end) - totalTime += end - }() - input := getInput(test.File) - if test.Handler == nil { - t.Fatalf("%s handler is nil", test.Name) - } - got, err := test.Handler(aoc.Context{Body: input, Logger: l}) - assert(t, err, test.Want, got) - }) - } -}