chore: use shoelace and picks theorem
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Go Test / build (pull_request) Successful in 57s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Go Test / build (pull_request) Successful in 57s
				
			This commit is contained in:
		
							parent
							
								
									b6501e80c9
								
							
						
					
					
						commit
						bcd90a57f3
					
				
							
								
								
									
										255
									
								
								day18/main.go
									
									
									
									
									
								
							
							
						
						
									
										255
									
								
								day18/main.go
									
									
									
									
									
								
							@ -3,9 +3,7 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
	_ "embed"
 | 
						_ "embed"
 | 
				
			||||||
	"encoding/hex"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,8 +24,8 @@ func (r result) String() string { return fmt.Sprintf("%#v", r) }
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func run(scan *bufio.Scanner) (*result, error) {
 | 
					func run(scan *bufio.Scanner) (*result, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var vecs []vec
 | 
						var vecsPT1 []vector
 | 
				
			||||||
	var vecs2 []vec
 | 
						var vecsPT2 []vector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for scan.Scan() {
 | 
						for scan.Scan() {
 | 
				
			||||||
		text := scan.Text()
 | 
							text := scan.Text()
 | 
				
			||||||
@ -36,199 +34,96 @@ func run(scan *bufio.Scanner) (*result, error) {
 | 
				
			|||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var s string
 | 
							v, color := fromLine(text)
 | 
				
			||||||
		v := vec{}
 | 
					
 | 
				
			||||||
		s, text, _ = strings.Cut(text, " ")
 | 
							vecsPT1 = append(vecsPT1, v)
 | 
				
			||||||
		v.Direction = direction(s[0])
 | 
							vecsPT2 = append(vecsPT2, fromColor(color))
 | 
				
			||||||
		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))
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &result{
 | 
						return &result{
 | 
				
			||||||
		valuePT1: findArea(vecs),
 | 
							valuePT1: findArea(vecsPT1),
 | 
				
			||||||
		valuePT2: skip("AOC_DAY18P2", func() int { return findArea(vecs2) }),
 | 
							valuePT2: findArea(vecsPT2),
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func findArea(vecs []vec) int {
 | 
					type vector struct {
 | 
				
			||||||
	var points []point
 | 
						offset point
 | 
				
			||||||
 | 
						scale  int
 | 
				
			||||||
	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 direction rune
 | 
					type point [2]int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					func (p point) add(a point) point {
 | 
				
			||||||
	U direction = 'U'
 | 
						return point{p[0] + a[0], p[1] + a[1]}
 | 
				
			||||||
	D direction = 'D'
 | 
					}
 | 
				
			||||||
	L direction = 'L'
 | 
					func (p point) scale(m int) point {
 | 
				
			||||||
	R direction = 'R'
 | 
						return point{p[0] * m, p[1] * m}
 | 
				
			||||||
)
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func opposite(d direction) direction {
 | 
					// numPoints the number of the points inside an outline plus the number of points in the outline
 | 
				
			||||||
	switch d {
 | 
					func numPoints(outline []point, borderLength int) int {
 | 
				
			||||||
	case U:
 | 
						// shoelace - find the float area in a shape
 | 
				
			||||||
		return D
 | 
						sum := 0
 | 
				
			||||||
	case D:
 | 
						for _, p := range pairwise(outline) {
 | 
				
			||||||
		return U
 | 
							row1, col1 := p[0][0], p[0][1]
 | 
				
			||||||
	case L:
 | 
							row2, col2 := p[1][0], p[1][1]
 | 
				
			||||||
		return R
 | 
					
 | 
				
			||||||
	case R:
 | 
							sum += row1*col2 - row2*col1
 | 
				
			||||||
		return L
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	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 {
 | 
					func pairwise[T any](arr []T) [][2]T {
 | 
				
			||||||
	Direction direction
 | 
						var pairs [][2]T
 | 
				
			||||||
	Steps     int
 | 
						for i := range arr[:len(arr)-1] {
 | 
				
			||||||
	Color     [3]byte
 | 
							pairs = append(pairs, [2]T{arr[i], arr[i+1]})
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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'
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return pairs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return vec{
 | 
					var OFFSET = map[string]point{
 | 
				
			||||||
		Direction: direction(d),
 | 
						"R": {0, 1},
 | 
				
			||||||
		Steps:     int(steps),
 | 
						"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 {
 | 
					func findArea(vecs []vector) int {
 | 
				
			||||||
	var zero T
 | 
						shoelace := []point{{0,0}}
 | 
				
			||||||
	if e, err := strconv.ParseBool(os.Getenv(env)); err == nil && e {
 | 
						borderLength := 0
 | 
				
			||||||
		return zero
 | 
					
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_ "embed"
 | 
						_ "embed"
 | 
				
			||||||
@ -33,13 +32,11 @@ func TestSolution(t *testing.T) {
 | 
				
			|||||||
	is := is.New(t)
 | 
						is := is.New(t)
 | 
				
			||||||
	scan := bufio.NewScanner(bytes.NewReader(input))
 | 
						scan := bufio.NewScanner(bytes.NewReader(input))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	os.Setenv("AOC_DAY18P2", "1")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	result, err := run(scan)
 | 
						result, err := run(scan)
 | 
				
			||||||
	is.NoErr(err)
 | 
						is.NoErr(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Log(result)
 | 
						t.Log(result)
 | 
				
			||||||
	is.True(result.valuePT1 < 68834) // first attempt too high.
 | 
						is.True(result.valuePT1 < 68834) // first attempt too high.
 | 
				
			||||||
	is.Equal(result.valuePT1, 46334)
 | 
						is.Equal(result.valuePT1, 46334)
 | 
				
			||||||
	is.Equal(result.valuePT2, 0)
 | 
						is.Equal(result.valuePT2, 102000662718092)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user