Compare commits

..

No commits in common. "3c2ea4ed9ebfebbedf0b4aefcda7529dfd62caa7" and "1a3374a5579ba8f226d6e3517494dae064f218de" have entirely different histories.

13 changed files with 267 additions and 312 deletions

View File

@ -1,8 +1,6 @@
package aoc_test package aoc_test
import ( import (
"fmt"
"sort"
"testing" "testing"
"github.com/matryer/is" "github.com/matryer/is"
@ -81,130 +79,15 @@ func TestList(t *testing.T) {
is.Equal(a, 5) is.Equal(a, 5)
} }
func TestPriorityQueue(t *testing.T) { func TestGraph(t *testing.T) {
is := is.New(t) g := aoc.Graph[int, uint](7)
g.AddEdge(0, 1, 2)
type elem [2]int g.AddEdge(0, 2, 6)
less := func(a, b elem) bool { g.AddEdge(1, 3, 5)
return b[0] < a[0] g.AddEdge(2, 3, 8)
} g.AddEdge(3, 4, 10)
g.AddEdge(3, 5, 15)
pq := aoc.PriorityQueue(less) g.AddEdge(4, 6, 2)
g.AddEdge(5, 6, 6)
pq.Enqueue(elem{1, 4}) // g.Dijkstra(0)
pq.Enqueue(elem{3, 2})
pq.Enqueue(elem{2, 3})
pq.Enqueue(elem{4, 1})
v, ok := pq.Dequeue()
is.True(ok)
is.Equal(v, elem{4, 1})
v, ok = pq.Dequeue()
is.True(ok)
is.Equal(v, elem{3, 2})
v, ok = pq.Dequeue()
is.True(ok)
is.Equal(v, elem{2, 3})
v, ok = pq.Dequeue()
is.True(ok)
is.Equal(v, elem{1, 4})
v, ok = pq.Dequeue()
is.True(!ok)
is.Equal(v, elem{})
}
func TestSet(t *testing.T) {
is := is.New(t)
s := aoc.Set(1, 2, 3)
is.True(!s.Has(0))
is.True(s.Has(1))
is.True(s.Has(2))
is.True(s.Has(3))
is.True(!s.Has(4))
s.Add(4)
is.True(s.Has(4))
items := s.Items()
sort.Ints(items)
is.Equal(items, []int{1, 2, 3, 4})
}
// func TestGraph(t *testing.T) {
// g := aoc.Graph[int, uint](7)
// g.AddEdge(0, 1, 2)
// g.AddEdge(0, 2, 6)
// g.AddEdge(1, 3, 5)
// g.AddEdge(2, 3, 8)
// g.AddEdge(3, 4, 10)
// g.AddEdge(3, 5, 15)
// g.AddEdge(4, 6, 2)
// g.AddEdge(5, 6, 6)
// // g.Dijkstra(0)
// }
func ExamplePriorityQueue() {
type memo struct {
pt int
score int
}
less := func(a, b memo) bool { return a.score < b.score }
adj := map[int][][2]int{
0: {{1, 2}, {2, 6}},
1: {{3, 5}},
2: {{3, 8}},
3: {{4, 10}, {5, 15}},
4: {{6, 2}},
5: {{6, 6}},
}
pq := aoc.PriorityQueue(less)
visited := aoc.Set([]int{}...)
dist := aoc.DefaultMap[int](int(^uint(0) >> 1))
dist.Set(0, 0)
pq.Enqueue(memo{0, 0})
for !pq.IsEmpty() {
m, _ := pq.Dequeue()
u := m.pt
if visited.Has(u) {
continue
}
visited.Add(u)
du, _ := dist.Get(u)
for _, edge := range adj[u] {
v, w := edge[0], edge[1]
dv, _ := dist.Get(v)
if !visited.Has(v) && du+w < dv {
dist.Set(v, du+w)
pq.Enqueue(memo{v, du + w})
}
}
}
items := dist.Items()
sort.Slice(items, func(i, j int) bool { return items[i].K < items[j].K })
for _, v := range items {
fmt.Printf("point %d is %d steps away.\n", v.K, v.V)
}
// Output:
// point 0 is 0 steps away.
// point 1 is 2 steps away.
// point 2 is 6 steps away.
// point 3 is 7 steps away.
// point 4 is 17 steps away.
// point 5 is 22 steps away.
// point 6 is 19 steps away.
} }

View File

@ -30,6 +30,7 @@ func TestExample1(t *testing.T) {
is.Equal(result.sum, 142) is.Equal(result.sum, 142)
} }
func TestExample2(t *testing.T) { func TestExample2(t *testing.T) {
is := is.New(t) is := is.New(t)
scan := bufio.NewScanner(bytes.NewReader(example2)) scan := bufio.NewScanner(bytes.NewReader(example2))

View File

@ -36,10 +36,11 @@ func TestSolution(t *testing.T) {
is.NoErr(err) is.NoErr(err)
t.Log(result) t.Log(result)
is.True(result.valuePT2 < 87286) // first submission is.True(result.valuePT2 < 87286) // first submission
is.True(result.valuePT2 < 87292) // second submission is.True(result.valuePT2 < 87292) // second submission
is.True(result.valuePT2 < 87287) // third submission is.True(result.valuePT2 < 87287) // third submission
is.Equal(result.valuePT1, 110407) is.Equal(result.valuePT1, 110407)
is.Equal(result.valuePT2, 87273) is.Equal(result.valuePT2, 87273)
} }

View File

@ -32,15 +32,15 @@ func run(scan *bufio.Scanner) (*result, error) {
options := make([]int, 2*(rows+cols)+2) options := make([]int, 2*(rows+cols)+2)
i := 0 i := 0
for j := 0; j <= rows-1; j++ { for j:=0; j<=rows-1; j++ {
options[i+0] = runCycle(m, ray{[2]int{j, -1}, RT}) options[i+0] = runCycle(m, ray{[2]int{j, -1}, RT})
options[i+1] = runCycle(m, ray{[2]int{j, cols}, LF}) options[i+1] = runCycle(m, ray{[2]int{j, cols}, LF})
i += 2 i+=2
} }
for j := 0; j <= cols-1; j++ { for j:=0; j<=cols-1; j++ {
options[i+0] = runCycle(m, ray{[2]int{-1, j}, DN}) options[i+0] = runCycle(m, ray{[2]int{-1, j}, DN})
options[i+1] = runCycle(m, ray{[2]int{rows, j}, UP}) options[i+1] = runCycle(m, ray{[2]int{rows, j}, UP})
i += 2 i+=2
} }
// fmt.Println(options) // fmt.Println(options)
@ -96,6 +96,7 @@ func (m *Map) Get(p [2]int) rune {
return (*m)[p[0]][p[1]] return (*m)[p[0]][p[1]]
} }
func runCycle(m Map, r ray) int { func runCycle(m Map, r ray) int {
current := r current := r
@ -185,4 +186,4 @@ func runCycle(m Map, r ray) int {
// } // }
return len(energized) return len(energized)
} }

View File

@ -47,4 +47,4 @@ func TestStack(t *testing.T) {
s := stack[int]{} s := stack[int]{}
s.Push(5) s.Push(5)
is.Equal(s.Pop(), 5) is.Equal(s.Pop(), 5)
} }

View File

@ -4,9 +4,9 @@ import (
"bufio" "bufio"
_ "embed" _ "embed"
"fmt" "fmt"
"sort"
aoc "go.sour.is/advent-of-code" aoc "go.sour.is/advent-of-code"
"golang.org/x/exp/maps"
) )
// var log = aoc.Log // var log = aoc.Log
@ -21,7 +21,7 @@ type result struct {
func (r result) String() string { return fmt.Sprintf("%#v", r) } 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 m aoc.Map[rune] var m Map
for scan.Scan() { for scan.Scan() {
text := scan.Text() text := scan.Text()
@ -35,97 +35,180 @@ func run(scan *bufio.Scanner) (*result, error) {
return &result, nil return &result, nil
} }
func search(m aoc.Map[rune], minSteps, maxSteps int) int { var (
type direction int8 ZERO = point{0, 0}
type rotate int8
const ( UP = point{-1, 0}
CW rotate = 1 DN = point{1, 0}
CCW rotate = -1 LF = point{0, -1}
) RT = point{0, 1}
var ( INF = int(^uint(0) >> 1)
U = aoc.Point{-1, 0} )
R = aoc.Point{0, 1}
D = aoc.Point{1, 0}
L = aoc.Point{0, -1}
)
var Directions = map[aoc.Point]direction{ type Map [][]rune
U: 0, // U
R: 1, // R func (m *Map) Get(p point) (point, int, bool) {
D: 2, // D if !m.Valid(p) {
L: 3, // L return [2]int{0, 0}, 0, false
} }
var DirectionIDX = maps.Keys(Directions)
return p, int((*m)[p[0]][p[1]] - '0'), true
}
func (m *Map) GetNeighbor(p point, d point) (point, int, bool) {
return m.Get(p.add(d))
}
func (m *Map) Size() (int, int) {
if m == nil || len(*m) == 0 {
return 0, 0
}
return len(*m), len((*m)[0])
}
func (m *Map) Neighbors(p point) []point {
var lis []point
for _, d := range []point{UP, DN, LF, RT} {
if p, _, ok := m.GetNeighbor(p, d); ok {
lis = append(lis, p)
}
}
return lis
}
func (m *Map) NeighborDirections(p point) []point {
var lis []point
for _, d := range []point{UP, DN, LF, RT} {
if m.Valid(p.add(d)) {
lis = append(lis, d)
}
}
return lis
}
func (m *Map) Valid(p point) bool {
rows, cols := m.Size() rows, cols := m.Size()
target := aoc.Point{rows - 1, cols - 1} return p[0] >= 0 && p[0] < rows && p[1] >= 0 && p[1] < cols
}
type position struct { type memo struct {
loc aoc.Point h int
direction aoc.Point s int
steps int p point
d point
}
func (memo) sort(a, b memo) bool {
if a.h != b.h {
return a.h < b.h
} }
step := func(p position) position { if a.s != b.s {
return position{p.loc.Add(p.direction), p.direction, p.steps + 1} return a.s < b.s
}
rotateAndStep := func(p position, towards rotate) position {
d := DirectionIDX[(int8(Directions[p.direction])+int8(towards)+4)%4]
return position{p.loc.Add(d), d, 1}
} }
type memo struct { if a.p != b.p {
cost int return a.p.less(b.p)
position
}
less := func(a, b memo) bool {
if a.cost != b.cost {
return a.cost < b.cost
}
if a.position.loc != b.position.loc {
return b.position.loc.Less(a.position.loc)
}
if a.position.direction != b.position.direction {
return b.position.direction.Less(a.position.direction)
}
return b.steps < a.steps
} }
pq := aoc.PriorityQueue(less) return a.d.less(b.d)
pq.Enqueue(memo{position: position{direction: D}}) }
pq.Enqueue(memo{position: position{direction: R}})
visited := aoc.Set[position]()
type priorityQueue[T any, U []T] struct {
elems U
sort func(a, b T) bool
}
func PriorityQueue[T any, U []T](sort func(a, b T) bool) *priorityQueue[T, U] {
return &priorityQueue[T, U]{sort: sort}
}
func (pq *priorityQueue[T, U]) Enqueue(elem T) {
pq.elems = append(pq.elems, elem)
sort.Slice(pq.elems, func(i, j int) bool { return pq.sort(pq.elems[i], pq.elems[j]) })
}
func (pq *priorityQueue[T, I]) IsEmpty() bool {
return len(pq.elems) == 0
}
func (pq *priorityQueue[T, I]) Dequeue() (T, bool) {
var elem T
if pq.IsEmpty() {
return elem, false
}
elem, pq.elems = pq.elems[0], pq.elems[1:]
return elem, true
}
func heuristic(m Map, p point) int {
rows, cols := m.Size()
return rows - p[0] + cols - p[1]
}
func search(m Map, minSize, maxSize int) int {
rows, cols := m.Size()
END := point{rows - 1, cols - 1}
visited := make(map[vector]int)
pq := PriorityQueue(memo{}.sort)
pq.Enqueue(memo{h: heuristic(m, point{0, 0}), p: point{0, 0}, d: DN})
for !pq.IsEmpty() { for !pq.IsEmpty() {
current, _ := pq.Dequeue() mem, _ := pq.Dequeue()
fmt.Println(mem)
if current.loc == target && current.steps >= minSteps { if mem.h > dmap(visited, vector{mem.p[0], mem.p[1], mem.d[0], mem.d[1]}, INF) {
return current.cost
}
seen := position{loc: current.loc, steps: current.steps}
if visited.Has(seen) {
continue continue
} }
visited.Add(seen)
if left := rotateAndStep(current.position, CCW); current.steps >= minSteps && m.Valid(left.loc) { if mem.p == END {
_, cost, _ := m.Get(left.loc) return mem.s
pq.Enqueue(memo{cost: current.cost + int(cost-'0'), position: left})
} }
if right := rotateAndStep(current.position, CW); current.steps >= minSteps && m.Valid(right.loc) { for _, nd := range m.NeighborDirections(mem.p) {
_, cost, _ := m.Get(right.loc) if nd[0] == 0 && mem.d == RT || nd[1] == 0 && mem.d == DN {
pq.Enqueue(memo{cost: current.cost + int(cost-'0'), position: right}) continue
} }
if forward := step(current.position); current.steps < maxSteps && m.Valid(forward.loc) { dscore := 0
_, cost, _ := m.Get(forward.loc)
pq.Enqueue(memo{cost: current.cost + int(cost-'0'), position: forward}) for _, size := range irange(1, maxSize+1) {
np := mem.p.add(nd.scale(size))
_, s, ok := m.Get(np)
if !ok {
break
}
dscore += s
pscore := mem.s + dscore
nh := heuristic(m, np) + pscore
vec := vector{np[0], np[1], nd[0], nd[1]}
if size >= minSize && nh < dmap(visited, vec, INF) {
pq.Enqueue(memo{nh, pscore, np, nd})
visited[vec] = nh
}
}
} }
} }
return -1 return 0
} }
func dmap[K comparable, V any](m map[K]V, k K, d V) V {
if v, ok := m[k]; ok {
return v
}
return d
}
func irange(a, b int) []int {
lis := make([]int, b-a)
for i := range lis {
lis[i] = i + a
}
return lis
}
type point [2]int
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 (p point) less(a point) bool { return p[0] < a[0] || p[1] < a[1] }
type vector [4]int

View File

@ -65,7 +65,7 @@ func fromLine(text string) (aoc.Vector, string) {
s, _, _ = strings.Cut(text, ")") s, _, _ = strings.Cut(text, ")")
return v, s return v, s
} }
func fromColor(c string) aoc.Vector { func fromColor(c string) aoc.Vector {
scale, _ := strconv.ParseInt(c[:5], 16, 64) scale, _ := strconv.ParseInt(c[:5], 16, 64)
offset := OFFSET_INDEXES[c[5]-'0'] offset := OFFSET_INDEXES[c[5]-'0']
@ -77,7 +77,7 @@ func fromColor(c string) aoc.Vector {
} }
func findArea(vecs []aoc.Vector) int { func findArea(vecs []aoc.Vector) int {
shoelace := []aoc.Point{{0, 0}} shoelace := []aoc.Point{{0,0}}
borderLength := 0 borderLength := 0
for _, vec := range vecs { for _, vec := range vecs {
@ -87,3 +87,4 @@ func findArea(vecs []aoc.Vector) int {
return aoc.NumPoints(shoelace, borderLength) return aoc.NumPoints(shoelace, borderLength)
} }

View File

@ -17,12 +17,6 @@ func (p Point) Add(a Point) Point {
func (p Point) Scale(m int) Point { func (p Point) Scale(m int) Point {
return Point{p[0] * m, p[1] * m} return Point{p[0] * m, p[1] * m}
} }
func (p Point) Less(b Point) bool {
if p[0] != b[0] {
return p[0] < b[0]
}
return p[1] < b[1]
}
func Transpose[T any](matrix [][]T) [][]T { func Transpose[T any](matrix [][]T) [][]T {
rows, cols := len(matrix), len(matrix[0]) rows, cols := len(matrix), len(matrix[0])
@ -55,24 +49,3 @@ func NumPoints(outline []Point, borderLength int) int {
// pick's theorem - find the number of points in a shape given its area // pick's theorem - find the number of points in a shape given its area
return (ABS(area) - borderLength/2 + 1) + borderLength return (ABS(area) - borderLength/2 + 1) + borderLength
} }
type Map[T any] [][]T
func (m *Map[T]) Get(p Point) (Point, T, bool) {
var zero T
if !m.Valid(p) {
return [2]int{0, 0}, zero, false
}
return p, (*m)[p[0]][p[1]], true
}
func (m *Map[T]) Size() (int, int) {
if m == nil || len(*m) == 0 {
return 0, 0
}
return len(*m), len((*m)[0])
}
func (m *Map[T]) Valid(p Point) bool {
rows, cols := m.Size()
return p[0] >= 0 && p[0] < rows && p[1] >= 0 && p[1] < cols
}

View File

@ -31,6 +31,7 @@ func Reverse[T any](arr []T) []T {
return arr return arr
} }
func SliceMap[T, U any](fn func(T) U, in ...T) []U { func SliceMap[T, U any](fn func(T) U, in ...T) []U {
lis := make([]U, len(in)) lis := make([]U, len(in))
for i := range lis { for i := range lis {
@ -53,4 +54,4 @@ func Pairwise[T any](arr []T) [][2]T {
pairs = append(pairs, [2]T{arr[i], arr[i+1]}) pairs = append(pairs, [2]T{arr[i], arr[i+1]})
} }
return pairs return pairs
} }

View File

@ -2,6 +2,7 @@ package aoc
import "fmt" import "fmt"
type Node[T any] struct { type Node[T any] struct {
value T value T
pos int pos int

View File

@ -41,6 +41,7 @@ func Logf(format string, v ...any) {
fmt.Fprintf(os.Stderr, format, v...) fmt.Fprintf(os.Stderr, format, v...)
} }
func ReadStringToInts(fields []string) []int { func ReadStringToInts(fields []string) []int {
return SliceMap(Atoi, fields...) return SliceMap(Atoi, fields...)
} }

View File

@ -4,32 +4,100 @@ import (
"sort" "sort"
) )
type priorityQueue[T any, U []T] struct { type PQElem[T any, I integer] struct {
elems U Value T
less func(a, b T) bool Priority I
}
type PQList[T any, I integer] []PQElem[T, I]
func (pq PQList[T, I]) Len() int {
return len(pq)
}
func (pq PQList[T, I]) Less(i int, j int) bool {
return pq[i].Priority < pq[j].Priority
}
func (pq PQList[T, I]) Swap(i int, j int) {
pq[i], pq[j] = pq[j], pq[i]
} }
func PriorityQueue[T any, U []T](less func(a, b T) bool) *priorityQueue[T, U] { var _ sort.Interface = (*PQList[rune, int])(nil)
return &priorityQueue[T, U]{less: less}
type PriorityQueue[T any, I integer] struct {
elem PQList[T, I]
} }
func (pq *priorityQueue[T, U]) Enqueue(elem T) {
pq.elems = append(pq.elems, elem) func (pq *PriorityQueue[T, I]) Enqueue(elem T, priority I) {
sort.Slice(pq.elems, func(i, j int) bool { return pq.less(pq.elems[j], pq.elems[i]) }) pq.elem = append(pq.elem, PQElem[T, I]{elem, priority})
sort.Sort(pq.elem)
} }
func (pq *priorityQueue[T, I]) IsEmpty() bool { func (pq *PriorityQueue[T, I]) IsEmpty() bool {
return len(pq.elems) == 0 return len(pq.elem) == 0
} }
func (pq *priorityQueue[T, I]) Dequeue() (T, bool) { func (pq *PriorityQueue[T, I]) Dequeue() (T, bool) {
var elem T var elem T
if pq.IsEmpty() { if pq.IsEmpty() {
return elem, false return elem, false
} }
pq.elems, elem = pq.elems[:len(pq.elems)-1], pq.elems[len(pq.elems)-1] elem, pq.elem = pq.elem[0].Value, pq.elem[1:]
return elem, true return elem, true
} }
type DS[T comparable] struct { type Vertex[V comparable, I integer] struct {
*priorityQueue[T, []T] to V
*set[T] score I
}
type graph[V comparable, I uinteger] struct {
adj map[V][]Vertex[V, I]
}
func Graph[V comparable, I uinteger](size int) *graph[V, I] {
return &graph[V, I]{
adj: make(map[V][]Vertex[V, I], size),
}
}
func (g *graph[V, I]) AddEdge(u, v V, w I) {
g.adj[u] = append(g.adj[u], Vertex[V, I]{to: v, score: w})
g.adj[v] = append(g.adj[v], Vertex[V, I]{to: u, score: w})
}
func (g *graph[V, I]) Dijkstra(m interface{ Get() }, src V) map[V]I {
pq := PriorityQueue[V, I]{}
dist := make(map[V]I, len(g.adj))
visited := make(map[V]bool, len(g.adj))
var INF I
INF = ^INF
pq.Enqueue(src, 0)
dist[src] = 0
for !pq.IsEmpty() {
u, _ := pq.Dequeue()
if _, ok := visited[u]; ok {
continue
}
visited[u] = true
for _, v := range g.adj[u] {
_, ok := visited[v.to]
var du, dv I
if d, inf := dist[u]; !inf {
du = INF
} else {
du = d
}
if d, inf := dist[v.to]; !inf {
dv = INF
} else {
dv = d
}
if !ok && du+v.score < dv {
dist[v.to] = du + v.score
pq.Enqueue(v.to, du+v.score)
}
}
}
return dist
} }

59
set.go
View File

@ -1,59 +0,0 @@
package aoc
import "golang.org/x/exp/maps"
type set[T comparable] map[T]struct{}
func Set[T comparable](arr ...T) set[T] {
m := make(set[T], len(arr))
for _, a := range arr {
m[a] = struct{}{}
}
return m
}
func (m *set[T]) Add(a T) {
(*m)[a] = struct{}{}
}
func (m *set[T]) Items() []T {
return maps.Keys(*m)
}
func (m *set[T]) Has(a T) bool {
var ok bool
_, ok = (*m)[a]
return ok
}
type defaultMap[K comparable, V any] struct {
m map[K]V
d V
}
func DefaultMap[K comparable, V any](d V) *defaultMap[K, V] {
return &defaultMap[K, V]{
make(map[K]V),
d,
}
}
func (m *defaultMap[K, V]) Set(k K, v V) {
m.m[k] = v
}
func (m *defaultMap[K, V]) Get(k K) (V, bool) {
if v, ok := m.m[k]; ok {
return v, true
}
return m.d, false
}
type pair[K, V any] struct {
K K
V V
}
func (m *defaultMap[K, V]) Items() []pair[K, V] {
var items = make([]pair[K, V], 0, len(m.m))
for k, v := range m.m {
items = append(items, pair[K, V]{k, v})
}
return items
}