From bcd90a57f395f90a71b6ca469e528387cf85083d Mon Sep 17 00:00:00 2001 From: xuu Date: Wed, 27 Dec 2023 13:46:42 -0700 Subject: [PATCH] chore: use shoelace and picks theorem --- day18/main.go | 255 +++++++++++++-------------------------------- day18/main_test.go | 5 +- 2 files changed, 76 insertions(+), 184 deletions(-) diff --git a/day18/main.go b/day18/main.go index f736d8a..a3220e5 100644 --- a/day18/main.go +++ b/day18/main.go @@ -3,9 +3,7 @@ package main import ( "bufio" _ "embed" - "encoding/hex" "fmt" - "os" "strconv" "strings" @@ -26,8 +24,8 @@ func (r result) String() string { return fmt.Sprintf("%#v", r) } func run(scan *bufio.Scanner) (*result, error) { - var vecs []vec - var vecs2 []vec + var vecsPT1 []vector + var vecsPT2 []vector for scan.Scan() { text := scan.Text() @@ -36,199 +34,96 @@ func run(scan *bufio.Scanner) (*result, error) { continue } - var s string - v := vec{} - s, text, _ = strings.Cut(text, " ") - v.Direction = direction(s[0]) - s, text, _ = strings.Cut(text, " ") - v.Steps = aoc.Atoi(s) - _, text, _ = strings.Cut(text, "#") - s, _, _ = strings.Cut(text, ")") - b, _ := hex.DecodeString(s) - copy(v.Color[:], b) - vecs = append(vecs, v) - vecs2 = append(vecs2, fromColor(s)) + v, color := fromLine(text) + + vecsPT1 = append(vecsPT1, v) + vecsPT2 = append(vecsPT2, fromColor(color)) } return &result{ - valuePT1: findArea(vecs), - valuePT2: skip("AOC_DAY18P2", func() int { return findArea(vecs2) }), + valuePT1: findArea(vecsPT1), + valuePT2: findArea(vecsPT2), }, nil } -func findArea(vecs []vec) int { - var points []point - - var x, y int - var minX, minY int - last := direction('S') - for _, v := range vecs { - // fmt.Println("pt ", i, v) - for i := 0; i < v.Steps; i++ { - switch v.Direction { - case 'U': - y++ - case 'D': - y-- - case 'R': - x++ - case 'L': - x-- - } - // fmt.Println("pt ", i, y, x) - - minX = min(minX, x) - minY = min(minY, y) - if len(points) > 0 { - points[len(points)-1].d = v.Direction - } - points = append(points, point{d: v.Direction, w: opposite(v.Direction), x: x, y: y, color: v.Color}) - last = v.Direction - } - } - _ = last - // points[0].w = last - points[len(points)-1].d = points[0].w - - adjX := aoc.ABS(min(0, minX)) - adjY := aoc.ABS(min(0, minY)) - fmt.Println("minX", minX, "minY", minY) - fmt.Println("adjX", adjX, "adjY", adjY) - - trace := make(map[int]map[int]point) - - minX, minY = 0, 0 - maxX, maxY := 0, 0 - - for i, p := range points { - - p.x += adjX - p.y += adjY - // fmt.Println("raw", i, p.x, p.y, string(p.w), string(p.d)) - - maxX = max(maxX, p.x) - maxY = max(maxY, p.y) - - points[i] = p - - if row, ok := trace[p.y]; true { - if !ok { - row = make(map[int]point) - } - row[p.x] = p - trace[p.y] = row - } - } - fmt.Println("maxX", maxX, "maxY", maxY) - - area := 0 - - for y := maxY; y >= 0; y-- { - row, ok := trace[y] - if !ok { - continue - } - - var last direction - p := row[0] - last = p.d - inLoop := false - - for x := 0; x <= maxX; x++ { - if p, ok := row[x]; ok { - // fmt.Println("vec", string(p.w), string(p.d)) - switch string([]rune{rune(p.w), rune(p.d)}) { - case "LD", "DL", "UD", "DU", "RD", "UU", "DR": - inLoop = !inLoop - } - if last != p.d { - last = p.d - } - - // fmt.Print(string(p.w)+string(p.d)) - - // On loop - area++ - - continue // 203338 - } - last = direction('0') - - if inLoop { - area++ - // fmt.Print("XX") - // } else { - // fmt.Print("..") - } - - } - // fmt.Println("") - fmt.Println(y, area) - } - return area +type vector struct { + offset point + scale int } -type direction rune +type point [2]int -const ( - U direction = 'U' - D direction = 'D' - L direction = 'L' - R direction = 'R' -) +func (p point) add(a point) point { + return point{p[0] + a[0], p[1] + a[1]} +} +func (p point) scale(m int) point { + return point{p[0] * m, p[1] * m} +} -func opposite(d direction) direction { - switch d { - case U: - return D - case D: - return U - case L: - return R - case R: - return L +// numPoints the number of the points inside an outline plus the number of points in the outline +func numPoints(outline []point, borderLength int) int { + // shoelace - find the float area in a shape + sum := 0 + for _, p := range pairwise(outline) { + row1, col1 := p[0][0], p[0][1] + row2, col2 := p[1][0], p[1][1] + + sum += row1*col2 - row2*col1 } - return '0' + area := sum / 2 + + // pick's theorem - find the number of points in a shape given its area + return (aoc.ABS(area) - borderLength/2 + 1) + borderLength } -type vec struct { - Direction direction - Steps int - Color [3]byte -} - -type point struct { - w direction - d direction - x, y int - color [3]byte -} - -func fromColor(c string) vec { - steps, _ := strconv.ParseInt(c[:5], 16, 64) - - d := '_' - switch c[5] { - case '0': - d = 'R' - case '1': - d = 'D' - case '2': - d = 'L' - case '3': - d = 'U' +func pairwise[T any](arr []T) [][2]T { + var pairs [][2]T + for i := range arr[:len(arr)-1] { + pairs = append(pairs, [2]T{arr[i], arr[i+1]}) } + return pairs +} - return vec{ - Direction: direction(d), - Steps: int(steps), +var OFFSET = map[string]point{ + "R": {0, 1}, + "D": {1, 0}, + "L": {0, -1}, + "U": {-1, 0}, +} +var OFFSET_INDEXES = maps.Values(OFFSET) + +func fromLine(text string) (vector, string) { + v := vector{} + s, text, _ := strings.Cut(text, " ") + v.offset = OFFSET[s] + + s, text, _ = strings.Cut(text, " ") + v.scale = aoc.Atoi(s) + + _, text, _ = strings.Cut(text, "(#") + s, _, _ = strings.Cut(text, ")") + return v, s +} + +func fromColor(c string) vector { + scale, _ := strconv.ParseInt(c[:5], 16, 64) + offset := OFFSET_INDEXES[c[5]-'0'] + + return vector{ + offset: offset, + scale: int(scale), } } -func skip[T any](env string, fn func() T) T { - var zero T - if e, err := strconv.ParseBool(os.Getenv(env)); err == nil && e { - return zero +func findArea(vecs []vector) int { + shoelace := []point{{0,0}} + borderLength := 0 + + for _, vec := range vecs { + scaled_offset := vec.offset.scale(vec.scale) + shoelace = append(shoelace, shoelace[len(shoelace)-1].add(scaled_offset)) + borderLength += vec.scale } - return fn() + return numPoints(shoelace, borderLength) } + diff --git a/day18/main_test.go b/day18/main_test.go index 15e4097..a7c83af 100644 --- a/day18/main_test.go +++ b/day18/main_test.go @@ -3,7 +3,6 @@ package main import ( "bufio" "bytes" - "os" "testing" _ "embed" @@ -33,13 +32,11 @@ func TestSolution(t *testing.T) { is := is.New(t) scan := bufio.NewScanner(bytes.NewReader(input)) - os.Setenv("AOC_DAY18P2", "1") - result, err := run(scan) is.NoErr(err) t.Log(result) is.True(result.valuePT1 < 68834) // first attempt too high. is.Equal(result.valuePT1, 46334) - is.Equal(result.valuePT2, 0) + is.Equal(result.valuePT2, 102000662718092) }