Compare commits

...

2 Commits

Author SHA1 Message Date
adcf94c5e5
day 9 2025-12-24 22:25:47 +02:00
bc96255925
day 9 2025-12-24 22:25:20 +02:00
5 changed files with 419 additions and 0 deletions

100
year25/day9/day9.go Normal file
View File

@ -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
}

27
year25/day9/day_test.go Normal file
View File

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

196
year25/day9/part2.go Normal file
View File

@ -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")
}
}

17
year25/day9/utils.go Normal file
View File

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

79
year25/day9/utils_test.go Normal file
View File

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