From bc96255925314d5534ef669a36bec4ea27b935a5 Mon Sep 17 00:00:00 2001 From: Gabriel Bizdoc Date: Wed, 24 Dec 2025 22:25:20 +0200 Subject: [PATCH] day 9 --- year25/day9/day9.go | 100 +++++++++++++++++++ year25/day9/day_test.go | 27 ++++++ year25/day9/part2.go | 196 ++++++++++++++++++++++++++++++++++++++ year25/day9/part2_mask.go | 136 ++++++++++++++++++++++++++ year25/day9/utils.go | 17 ++++ year25/day9/utils_test.go | 79 +++++++++++++++ 6 files changed, 555 insertions(+) create mode 100644 year25/day9/day9.go create mode 100644 year25/day9/day_test.go create mode 100644 year25/day9/part2.go create mode 100644 year25/day9/part2_mask.go create mode 100644 year25/day9/utils.go create mode 100644 year25/day9/utils_test.go diff --git a/year25/day9/day9.go b/year25/day9/day9.go new file mode 100644 index 0000000..203b657 --- /dev/null +++ b/year25/day9/day9.go @@ -0,0 +1,100 @@ +package day9 + +import ( + "bytes" + "cmp" + "fmt" + "slices" + "strconv" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" + "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" +) + +const lineIndex = 0 +const colIndex = 1 +const dot = '.' + +type Rectangle struct { + Point1, Point2 int + Top, Bottom geometry.Point + Area float64 +} + +func (r Rectangle) String() string { + v := r.Vertexes() + var b bytes.Buffer + fmt.Fprintln(&b, r.Top, r.Bottom) + for _, v := range v { + fmt.Fprintf(&b, "[{x:%2d y:%2d} -> {x:%2d y:%2d}] ", v.Start.Line, v.Start.Col, v.End.Line, v.End.Col) + } + fmt.Fprintf(&b, " a = %f", r.Area) + return b.String() +} +func (r Rectangle) Vertexes() (v [4]Vertex) { + v[0] = Vertex{ + Start: r.Top, + End: geometry.NewPoint(r.Top.Line, r.Bottom.Col), + Facing: geometry.DirectionRight, + } + v[1] = Vertex{ + Start: v[0].End, + End: r.Bottom, + Facing: geometry.DirectionDown, + } + v[2] = Vertex{ + Start: v[1].End, + End: geometry.NewPoint(r.Bottom.Line, r.Top.Col), + Facing: geometry.DirectionLeft, + } + v[3] = Vertex{ + Start: v[2].End, + End: r.Top, + Facing: geometry.DirectionUp, + } + + return +} + +func Part1(ctx aoc.Context) (int, error) { + g, err := geometry.ScannerToGrid(ctx.Scanner(), ",", strconv.Atoi) + if err != nil { + return 0, fmt.Errorf("day9: failed to parse intput %w", err) + } + points := geometry.UnsafeGridData(g) + for _, point := range points { + point[1], point[0] = point[0], point[1] + } + return int(day9FindRectangles(points)[0].Area), nil +} + +func day9FindRectangles(points [][]int) []Rectangle { + var pairs []Rectangle + var abs = func(a int) int { + if a < 0 { + a = -a + } + return a + } + for i, p1 := range points { + for j, p2 := range points[i+1:] { + x := abs(p1[colIndex]-p2[colIndex]) + 1 + y := abs(p1[lineIndex]-p2[lineIndex]) + 1 + + d := float64(x) * float64(y) + //fmt.Printf("%d,%d - %d,%d (%d-%d)%g\n", p1[colIndex], p1[lineIndex], p2[colIndex], p2[lineIndex], x, y, d) + pairs = append(pairs, Rectangle{ + Point1: i, + Point2: i + 1 + j, + Top: geometry.NewPoint(min(p1[lineIndex], p2[lineIndex]), min(p1[colIndex], p2[colIndex])), + Bottom: geometry.NewPoint(max(p1[lineIndex], p2[lineIndex]), max(p1[colIndex], p2[colIndex])), + Area: d, + }) + } + } + + slices.SortFunc(pairs, func(a, b Rectangle) int { + return cmp.Compare(b.Area, a.Area) // reverse order + }) + return pairs +} diff --git a/year25/day9/day_test.go b/year25/day9/day_test.go new file mode 100644 index 0000000..bcbf89a --- /dev/null +++ b/year25/day9/day_test.go @@ -0,0 +1,27 @@ +package day9 + +import ( + "testing" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/tests" +) + +func TestPart1Example(t *testing.T) { + const want = 50 + tests.Test(t, "example.txt", tests.Handler(Part1), want) +} + +func TestPart1(t *testing.T) { + const want = 4771508457 + tests.Test(t, "input.txt", tests.Handler(Part1), want) +} + +func TestPart2Example(t *testing.T) { + const want = 24 + tests.Test(t, "example.txt", tests.Handler(Part2), want) +} +func TestPart2(t *testing.T) { + const want = 1539809693 + // 122760 too low + tests.Test(t, "input.txt", tests.Handler(Part2), want) +} diff --git a/year25/day9/part2.go b/year25/day9/part2.go new file mode 100644 index 0000000..72f1758 --- /dev/null +++ b/year25/day9/part2.go @@ -0,0 +1,196 @@ +package day9 + +import ( + "errors" + "fmt" + "strconv" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" + "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" +) + +const DEBUG = false + +var debugGrid geometry.Grid[byte] + +func initDebugGrid(points [][]int) { + if !DEBUG { + return + } + var gridEnd geometry.Point + for _, point := range points { + gridEnd.Line = max(point[0], gridEnd.Line) + gridEnd.Col = max(point[1], gridEnd.Col) + } + gridEnd = gridEnd.Add(geometry.NewPoint(3, 3)) + debugGrid = geometry.NewGrid[byte](gridEnd.Line, gridEnd.Col) + debugGrid.Fill('.') +} + +func Part2(ctx aoc.Context) (result int, err error) { + // parse input + // generate all possible squares (check part 1) + // check if any line is cut by any polygon line + // check if all corners are inside the polygon + // check if corners are on the edge + + g, err := geometry.ScannerToGrid(ctx.Scanner(), ",", strconv.Atoi) + if err != nil { + return 0, fmt.Errorf("day9: failed to parse intput %w", err) + } + points := geometry.UnsafeGridData(g) + + for _, point := range points { + point[1], point[0] = point[0], point[1] + } + + initDebugGrid(points) + mainPolygon := createMainPolygon(points) + rectangles := day9FindRectangles(points) + + for _, rectangle := range rectangles { + rectangleVertices := rectangle.Vertexes() + + if DEBUG { + dg := debugGrid.Clone() + for _, v := range rectangleVertices { + for a := v.Start; a != v.End; a = a.MoveDirection(v.Facing) { + dg.Set(a, '*') + } + } + fmt.Println(dg) + fmt.Println() + } + + if !isP1InsideP2(rectangleVertices[:], mainPolygon.Vertices) { + continue + } + if polygonsIntersect(rectangleVertices[:], mainPolygon.Vertices) { + continue + } + + return int(rectangle.Area), nil + } + + return 0, errors.New("no solution found") +} + +func isP1InsideP2(p1, p2 []Vertex) bool { + for _, rectangleVertex := range p1 { + corner := rectangleVertex.Start // for each point of retangle + // check if corner is on vertex. if yes we are fine + // count vertexes + verticesPassed := 0 + for _, v := range p2 { + if isPointOnLine(corner, v) { + verticesPassed = 1 + break + } + if v.Start.Col == v.End.Col { // is vertical + if v.Start.Line <= corner.Line && corner.Line <= v.End.Line { + verticesPassed += 1 + } + } + } + if verticesPassed&1 == 0 { + return false + } + } + return true +} + +func isPointOnLine(p geometry.Point, l Vertex) bool { + v := p.Subs(l.Start) + w := l.End.Subs(l.Start) + + if v.Line*w.Col != v.Col*w.Line { + return false + } + + return w.Dot(p.Subs(l.End)) <= 0 +} +func polygonsIntersect(p1, p2 []Vertex) bool { + for _, squareVertex := range p1 { + // vertices can touch + squareVertex.Start = squareVertex.Start.MoveDirection(squareVertex.Facing) + squareVertex.End = geometry.NewDirectionalPointFromPoint(squareVertex.End, squareVertex.Facing).MoveBackward().Point() + + for _, mainVertex := range p2 { + if verticesIntersect(squareVertex, mainVertex) { + if DEBUG { + dg := debugGrid.Clone() + for a := squareVertex.Start; a != squareVertex.End; a = a.MoveDirection(squareVertex.Facing) { + dg.Set(a, '*') + } + for a := mainVertex.Start; a != mainVertex.End; a = a.MoveDirection(mainVertex.Facing) { + if dg.At(a) == '*' { + dg.Set(a, '+') + } else { + dg.Set(a, '@') + } + } + fmt.Println(dg) + fmt.Println() + } + return true + } + } + } + return false +} + +type Polygon struct { + Vertices []Vertex +} + +type Vertex struct { + Start geometry.Point + End geometry.Point + Facing geometry.Direction +} + +func createMainPolygon(vertexes [][]int) (p Polygon) { + p.Vertices = make([]Vertex, 0, len(vertexes)+1) + + prev := vertexes[len(vertexes)-1] + for i := range vertexes { + curr := vertexes[i] + v := Vertex{ + Start: geometry.NewPoint(prev[0], prev[1]), + End: geometry.NewPoint(curr[0], curr[1]), + } + v.Facing = computeDirection(v.Start, v.End) + p.Vertices = append(p.Vertices, v) + prev = curr + } + return +} + +func computeDirection(p1, p2 geometry.Point) geometry.Direction { // todo: move to devkit + switch { + case p1.Col == p2.Col: + switch { + case p1.Line == p2.Line: + panic("p1 and p2 do not form a line") + case p1.Line < p2.Line: + return geometry.DirectionDown + case p1.Line > p2.Line: + return geometry.DirectionUp + default: + panic("unreachable") + } + case p1.Line == p2.Line: + switch { + case p1.Col == p2.Col: + panic("p1 and p2 do not form a line") + case p1.Col < p2.Col: + return geometry.DirectionRight + case p1.Col > p2.Col: + return geometry.DirectionLeft + default: + panic("unreachable") + } + default: + panic("unreachable") + } +} diff --git a/year25/day9/part2_mask.go b/year25/day9/part2_mask.go new file mode 100644 index 0000000..0faa5c7 --- /dev/null +++ b/year25/day9/part2_mask.go @@ -0,0 +1,136 @@ +package day9 + +//) +// +//func Part2BF(ctx aoc.Context) (int, error) { +// g, err := geometry.ScannerToGrid(ctx.Scanner(), ",", strconv.Atoi) +// if err != nil { +// return 0, fmt.Errorf("day9: failed to parse intput %w", err) +// } +// points := geometry.UnsafeGridData(g) +// +// //data := g.Data() +// // +// //minx, miny := math.MaxInt, math.MaxInt +// //for i := range g.Rows() { +// // minx = min(data[i][0], minx) +// // miny = min(data[i][1], miny) +// //} +// //for i := range g.Rows() { +// // data[i][0] -= minx +// // data[i][1] -= miny +// // if data[i][1] == 0 { +// // fmt.Println() +// // } +// //} +// //g = geometry.GridFrom(data) +// +// bottomRight := []int{0, 0} +// for _, p := range points { +// bottomRight[colIndex] = max(bottomRight[colIndex], p[colIndex]) +// bottomRight[lineIndex] = max(bottomRight[lineIndex], p[lineIndex]) +// } +// bottomRight[colIndex] += 3 +// bottomRight[lineIndex] += 2 +// +// mask := geometry.NewGrid[byte](bottomRight[lineIndex], bottomRight[colIndex]) +// //data := geometry.UnsafeGridData(mask) +// //for i := 0; i < mask.Rows(); i++ { +// // for j := 0; j < mask.Cols(); j++ { +// // data[i][j] = dot +// // } +// //} +// //mask.Fill(dot) +// for _, point := range points { +// p := geometry.NewPoint(point[lineIndex], point[colIndex]) +// mask.Set(p, '#') +// } +// +// fillGrid := func(mask geometry.Grid[byte], start, end []int) { +// a := geometry.NewPoint(start[lineIndex], start[colIndex]).ToDirectional(0) +// b := geometry.NewPoint(end[lineIndex], end[colIndex]).ToDirectional(0) +// +// var d geometry.Direction +// if a.GetLine() == b.GetLine() { +// if a.GetCol() < b.GetCol() { +// d = geometry.DirectionRight +// } else { +// d = geometry.DirectionLeft +// } +// } else { +// if a.GetLine() < b.GetLine() { +// d = geometry.DirectionDown +// } else { +// d = geometry.DirectionUp +// } +// } +// a = a.SetDirection(d) +// b = b.SetDirection(d).MoveBackward() +// +// for a != b { +// a = a.MoveForward() +// mask.Set(a, 'X') +// } +// } +// +// var prev = points[len(points)-1] +// for i := 0; i < len(points); i++ { +// next := points[i] +// if prev[colIndex] == next[colIndex] || prev[lineIndex] == next[lineIndex] { +// fillGrid(mask, prev, next) +// } else { +// panic("day9: points out of range") +// } +// prev = next +// } +// +// stack := dsa.NewStack[geometry.Point]() +// for i, line := range geometry.UnsafeGridData(mask) { +// walls := 0 +// for j, point := range line { +// if point == 'X' || point == '#' { +// walls += 1 +// } +// if point == dot && walls == 1 { +// stack.Push(geometry.NewPoint(i, j)) +// break +// } +// } +// if !stack.IsEmpty() { +// break +// } +// } +// for !stack.IsEmpty() { +// p := stack.Pop() +// if mask.At(p) == dot { +// mask.Set(p, 'Y') +// stack.Push(p.MoveUp()) +// stack.Push(p.MoveLeft()) +// stack.Push(p.MoveRight()) +// stack.Push(p.MoveDown()) +// } +// } +// +// for _, pair := range day9FindRectangles(points) { +// p1 := points[pair.Point1] +// p2 := points[pair.Point2] +// +// start := geometry.NewPoint(min(p1[lineIndex], p2[lineIndex]), min(p1[colIndex], p2[colIndex])) +// end := geometry.NewPoint(max(p1[lineIndex], p2[lineIndex]), max(p1[colIndex], p2[colIndex])) +// +// for i := start.Line; i <= end.Line; i++ { +// for j := start.Col; j <= end.Col; j++ { +// p := geometry.NewPoint(i, j) +// old := mask.At(p) +// mask.Set(p, '*') +// mask.Set(p, old) +// if mask.At(p) == dot { +// goto skip +// } +// } +// } +// return int(pair.Area), nil +// skip: +// } +// panic("day9: no solution found") +//} diff --git a/year25/day9/utils.go b/year25/day9/utils.go new file mode 100644 index 0000000..604ae79 --- /dev/null +++ b/year25/day9/utils.go @@ -0,0 +1,17 @@ +package day9 + +import "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" + +func ccw(A, B, C geometry.Point) bool { + return (C.Line-A.Line)*(B.Col-A.Col) > (B.Line-A.Line)*(C.Col-A.Col) +} + +// https://stackoverflow.com/questions/3838329/how-can-i-check-if-two-segments-intersect +// intersect Return true if line segments AB and CD intersect +func intersect(A, B, C, D geometry.Point) bool { + return ccw(A, C, D) != ccw(B, C, D) && ccw(A, B, C) != ccw(A, B, D) +} + +func verticesIntersect(v1, v2 Vertex) bool { + return intersect(v1.Start, v1.End, v2.Start, v2.End) +} diff --git a/year25/day9/utils_test.go b/year25/day9/utils_test.go new file mode 100644 index 0000000..ea0c9e2 --- /dev/null +++ b/year25/day9/utils_test.go @@ -0,0 +1,79 @@ +package day9 + +import ( + "testing" + + "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" +) + +func Test_intersect(t *testing.T) { + type args struct { + A geometry.Point + B geometry.Point + C geometry.Point + D geometry.Point + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "no intersection", + want: false, + args: args{ + A: geometry.Point{Line: 3, Col: 3}, + B: geometry.Point{Line: 3, Col: 10}, + C: geometry.Point{Line: 4, Col: 5}, + D: geometry.Point{Line: 4, Col: 10}, + }, + }, + { + name: "intersection", + want: true, + args: args{ + A: geometry.Point{Line: 3, Col: 3}, + B: geometry.Point{Line: 3, Col: 10}, + C: geometry.Point{Line: 2, Col: 5}, + D: geometry.Point{Line: 4, Col: 5}, + }, + }, + { + name: "on the edge", + want: false, + args: args{ + A: geometry.Point{Line: 3, Col: 3}, + B: geometry.Point{Line: 3, Col: 10}, + C: geometry.Point{Line: 3, Col: 3}, + D: geometry.Point{Line: 3, Col: 10}, + }, + }, + //{ + // name: "on the edge but overflow", + // want: false, + // args: args{ + // A: geometry.Point{Line: 3, Col: 3}, + // B: geometry.Point{Line: 3, Col: 10}, + // C: geometry.Point{Line: 3, Col: 3}, + // D: geometry.Point{Line: 3, Col: 11}, + // }, + //}, + //{ + // name: "corner", + // want: false, + // args: args{ + // A: geometry.Point{Line: 3, Col: 2}, + // B: geometry.Point{Line: 3, Col: 9}, + // C: geometry.Point{Line: 5, Col: 2}, + // D: geometry.Point{Line: 3, Col: 2}, + // }, + //}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := intersect(tt.args.A, tt.args.B, tt.args.C, tt.args.D); got != tt.want { + t.Errorf("intersect() = %v, want %v", got, tt.want) + } + }) + } +}