chore: cleanup and add tools

This commit is contained in:
xuu 2024-10-30 13:32:44 -06:00
parent 04bbac8559
commit e046a6c06d
Signed by: xuu
GPG Key ID: 8B3B0604F164E04F
11 changed files with 325 additions and 53 deletions

View File

@ -10,7 +10,7 @@ import (
// var log = aoc.Log // var log = aoc.Log
func main() { aoc.MustResult(aoc.Runner(run)) } func main() { aoc.MustResult(aoc.Runner(runner(64))) }
type result struct { type result struct {
valuePT1 int valuePT1 int
@ -19,12 +19,81 @@ 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 runner(rounds int) func(scan *bufio.Scanner) (*result, error) {
return func(scan *bufio.Scanner) (*result, error) {
var garden garden
for scan.Scan() { for scan.Scan() {
_ = scan.Text() txt := scan.Text()
garden.m = append(garden.m, []rune(txt))
for i, c := range txt {
if c == 'S' {
garden.start[0] = len(garden.m) - 1
garden.start[1] = i
}
}
}
garden.Step(rounds)
return &result{
valuePT1: len(garden.steps[len(garden.steps)-1]),
}, nil
}
} }
return &result{}, nil type garden struct {
start aoc.Point[int]
m [][]rune
steps []aoc.Set[aoc.Point[int]]
}
func (g *garden) Neighbors(p aoc.Point[int]) []aoc.Point[int] {
var neighbors []aoc.Point[int]
for _, n := range []aoc.Point[int]{
{p[0] - 1, p[1]},
{p[0] + 1, p[1]},
{p[0], p[1] - 1},
{p[0], p[1] + 1},
} {
if n[0] >= 0 && n[0] < len(g.m) && n[1] >= 0 && n[1] < len(g.m[0]) && g.m[n[0]][n[1]] != '#' {
neighbors = append(neighbors, n)
}
}
return neighbors
}
func (g *garden) Step(n int) {
if len(g.steps) == 0 {
g.steps = append(g.steps, aoc.NewSet(g.start))
}
for step := range(n) {
g.steps = append(g.steps, aoc.NewSet[aoc.Point[int]]())
for p := range g.steps[step] {
for _, n := range g.Neighbors(p) {
g.steps[step+1].Add(n)
}
}
}
}
func (g garden) String() string {
var b []rune
for i, line := range g.m {
if i == g.start[0] {
line[g.start[1]] = 'X'
}
if steps := len(g.steps) - 1; steps > 0 {
for p := range g.steps[len(g.steps)-1] {
if p[0] == i {
line[p[1]] = 'O'
}
}
}
b = append(b, line...)
b = append(b, '\n')
}
return string(b)
} }

View File

@ -20,11 +20,11 @@ func TestExample(t *testing.T) {
is := is.New(t) is := is.New(t)
scan := bufio.NewScanner(bytes.NewReader(example)) scan := bufio.NewScanner(bytes.NewReader(example))
result, err := run(scan) result, err := runner(6)(scan)
is.NoErr(err) is.NoErr(err)
t.Log(result) t.Log(result)
is.Equal(result.valuePT1, 0) is.Equal(result.valuePT1, 16)
is.Equal(result.valuePT2, 0) is.Equal(result.valuePT2, 0)
} }
@ -32,10 +32,10 @@ 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))
result, err := run(scan) result, err := runner(64)(scan)
is.NoErr(err) is.NoErr(err)
t.Log(result) t.Log(result)
is.Equal(result.valuePT1, 0) is.Equal(result.valuePT1, 3709)
is.Equal(result.valuePT2, 0) is.Equal(result.valuePT2, 0)
} }

View File

@ -9,7 +9,6 @@ import (
aoc "go.sour.is/advent-of-code" aoc "go.sour.is/advent-of-code"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {
is := is.New(t) is := is.New(t)
@ -56,7 +55,6 @@ func TestPriorityQueue(t *testing.T) {
is.True(v == nil) is.True(v == nil)
} }
func ExamplePriorityQueue() { func ExamplePriorityQueue() {
type memo struct { type memo struct {
pt int pt int
@ -74,7 +72,7 @@ func ExamplePriorityQueue() {
} }
pq := aoc.PriorityQueue(less) pq := aoc.PriorityQueue(less)
visited := aoc.Set([]int{}...) visited := aoc.NewSet([]int{}...)
dist := aoc.DefaultMap[int](int(^uint(0) >> 1)) dist := aoc.DefaultMap[int](int(^uint(0) >> 1))
dist.Set(0, 0) dist.Set(0, 0)
@ -117,25 +115,7 @@ func ExamplePriorityQueue() {
// point 5 is 22 steps away. // point 5 is 22 steps away.
// point 6 is 19 steps away. // point 6 is 19 steps away.
} }
func TestGraph(t *testing.T) {
is := is.New(t)
var adjacencyList = map[int][]int{
2: {3, 5, 1},
1: {2, 4},
3: {6, 2},
4: {1, 5, 7},
5: {2, 6, 8, 4},
6: {3, 0, 9, 5},
7: {4, 8},
8: {5, 9, 7},
9: {6, 0, 8},
}
g := aoc.Graph(aoc.WithAdjacencyList[int, int](adjacencyList))
is.Equal(g.Neighbors(1), []int{2, 4})
is.Equal(map[int][]int(g.AdjacencyList()), adjacencyList)
}
func ExampleFibHeap() { func ExampleFibHeap() {
type memo struct { type memo struct {
@ -154,7 +134,7 @@ func ExampleFibHeap() {
} }
pq := aoc.FibHeap(less) pq := aoc.FibHeap(less)
visited := aoc.Set([]int{}...) visited := aoc.NewSet([]int{}...)
dist := aoc.DefaultMap[int](int(^uint(0) >> 1)) dist := aoc.DefaultMap[int](int(^uint(0) >> 1))
dist.Set(0, 0) dist.Set(0, 0)

View File

@ -3,6 +3,7 @@ package aoc
import ( import (
"cmp" "cmp"
"fmt" "fmt"
"iter"
"sort" "sort"
"strings" "strings"
@ -156,10 +157,10 @@ func CompressMap[C number, N comparable](p pather[C, N], start N) pather[C, N] {
return &cmap[C, N]{base: p, neighbors: visited} return &cmap[C, N]{base: p, neighbors: visited}
} }
type adjacencyList[V any, C comparable] map[C][]V type adjacencyList[V comparable] map[V][]V
type graph[V any, W cmp.Ordered, C comparable] map[C]*vertex[V, W] type graph[V comparable, W cmp.Ordered] map[V]*vertex[V, W]
type graphOption[V any, W cmp.Ordered, C comparable] func(g *graph[V, W, C]) type graphOption[V comparable, W cmp.Ordered] func(g *graph[V, W])
type vertex[V any, W cmp.Ordered] struct { type vertex[V comparable, W cmp.Ordered] struct {
Value V Value V
Edges edges[V, W] Edges edges[V, W]
} }
@ -173,27 +174,27 @@ func (v *vertex[V, W]) Neighbors() []V {
return nbs return nbs
} }
type edge[V any, W cmp.Ordered] struct { type edge[V comparable, W cmp.Ordered] struct {
Vertex *vertex[V, W] Vertex *vertex[V, W]
Weight W Weight W
} }
type edges[V any, W cmp.Ordered] []edge[V, W] type edges[V comparable, W cmp.Ordered] []edge[V, W]
func (e edges[V, W]) Len() int { return len(e) } func (e edges[V, W]) Len() int { return len(e) }
func (e edges[V, W]) Less(i, j int) bool { return e[i].Weight < e[j].Weight } func (e edges[V, W]) Less(i, j int) bool { return e[i].Weight < e[j].Weight }
func (e edges[V, W]) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e edges[V, W]) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func Graph[V any, W cmp.Ordered, C comparable](opts ...graphOption[V, W, C]) *graph[V, W, C] { func Graph[V comparable, W cmp.Ordered](opts ...graphOption[V, W]) *graph[V, W] {
g := make(graph[V, W, C]) g := make(graph[V, W])
for _, opt := range opts { for _, opt := range opts {
opt(&g) opt(&g)
} }
return &g return &g
} }
func (g *graph[V, W, C]) AddVertex(id C, value V) { func (g *graph[V, W]) AddVertex(id V, value V) {
(*g)[id] = &vertex[V, W]{Value: value} (*g)[id] = &vertex[V, W]{Value: value}
} }
func (g *graph[V, W, C]) AddEdge(from, to C, w W) { func (g *graph[V, W]) AddEdge(from, to V, w W) {
if g == nil { if g == nil {
return return
} }
@ -206,15 +207,15 @@ func (g *graph[V, W, C]) AddEdge(from, to C, w W) {
(*g)[from].Edges = append((*g)[from].Edges, edge[V, W]{(*g)[to], w}) (*g)[from].Edges = append((*g)[from].Edges, edge[V, W]{(*g)[to], w})
} }
func (g *graph[V, W, C]) Neighbors(v C) []V { func (g *graph[V, W]) Neighbors(v V) []V {
if g == nil { if g == nil {
return nil return nil
} }
return (*g)[v].Neighbors() return (*g)[v].Neighbors()
} }
func (g *graph[V, W, C]) AdjacencyList() adjacencyList[V, C] { func (g *graph[V, W]) AdjacencyList() adjacencyList[V] {
m := make(map[C][]V) m := make(map[V][]V)
for id, v := range *g { for id, v := range *g {
if len(v.Edges) == 0 { if len(v.Edges) == 0 {
continue continue
@ -224,9 +225,9 @@ func (g *graph[V, W, C]) AdjacencyList() adjacencyList[V, C] {
return m return m
} }
func WithAdjacencyList[W cmp.Ordered, C comparable](list adjacencyList[C, C]) graphOption[C, W, C] { func WithAdjacencyList[W cmp.Ordered, V comparable](list adjacencyList[V]) graphOption[V, W] {
var zeroW W var zeroW W
return func(g *graph[C, W, C]) { return func(g *graph[V, W]) {
for vertex, edges := range list { for vertex, edges := range list {
if _, ok := (*g)[vertex]; !ok { if _, ok := (*g)[vertex]; !ok {
g.AddVertex(vertex, vertex) g.AddVertex(vertex, vertex)
@ -244,3 +245,53 @@ func WithAdjacencyList[W cmp.Ordered, C comparable](list adjacencyList[C, C]) gr
} }
} }
} }
func (g *graph[V, W]) BFS(start V) iter.Seq[V] {
visited := make(map[V]bool)
stack := NewStack(start)
return func(yield func(V) bool) {
for !stack.IsEmpty() {
current := stack.Pull()
if visited[current] {
continue
}
visited[current] = true
if !yield(current) {
return
}
neighbors := g.Neighbors(current)
for _, n := range neighbors {
if !visited[n] {
stack.Push(n)
}
}
}
}
}
// DFS returns a sequence of vertices in depth-first order, starting at the
// given vertex.
func (g *graph[V, W]) DFS(start V) iter.Seq[V] {
visited := make(map[V]bool)
stack := NewStack(start)
return func(yield func(V) bool) {
for !stack.IsEmpty() {
current := stack.Pop()
if visited[current] {
continue
}
visited[current] = true
if !yield(current) {
return
}
for _, n := range Reverse(g.Neighbors(current)) {
if !visited[n] {
stack.Push(n)
}
}
}
}
}

75
grids_test.go Normal file
View File

@ -0,0 +1,75 @@
package aoc_test
import (
"iter"
"testing"
"github.com/matryer/is"
aoc "go.sour.is/advent-of-code"
)
func TestGraph(t *testing.T) {
is := is.New(t)
var adjacencyList = map[int][]int{
2: {3, 5, 1},
1: {2, 4},
3: {6, 2},
4: {1, 5, 7},
5: {2, 6, 8, 4},
6: {3, 0, 9, 5},
7: {4, 8},
8: {5, 9, 7},
9: {6, 0, 8},
}
g := aoc.Graph(aoc.WithAdjacencyList[int](adjacencyList))
is.Equal(g.Neighbors(1), []int{2, 4})
is.Equal(map[int][]int(g.AdjacencyList()), adjacencyList)
}
func TestGraphDFS(t *testing.T) {
is := is.New(t)
var adjacencyList = map[int][]int{
2: {3, 5, 1},
1: {2, 4},
3: {6, 2},
4: {1, 5, 7},
5: {2, 6, 8, 4},
6: {3, 0, 9, 5},
7: {4, 8},
8: {5, 9, 7},
9: {6, 0, 8},
}
g := aoc.Graph(aoc.WithAdjacencyList[int](adjacencyList))
is.Equal(toList(g.DFS(6)), []int{6, 3, 2, 5, 8, 9, 0, 7, 4, 1})
}
func TestGraphBFS(t *testing.T) {
is := is.New(t)
var adjacencyList = map[int][]int{
2: {3, 5, 1},
1: {2, 4},
3: {6, 2},
4: {1, 5, 7},
5: {2, 6, 8, 4},
6: {3, 0, 9, 5},
7: {4, 8},
8: {5, 9, 7},
9: {6, 0, 8},
}
g := aoc.Graph(aoc.WithAdjacencyList[int](adjacencyList))
is.Equal(toList(g.BFS(6)), []int{6, 3, 0, 9, 5, 2, 8, 4, 1, 7})
}
func toList[T any](seq iter.Seq[T]) []T {
var list []T
for v := range seq {
list = append(list, v)
}
return list
}

View File

@ -42,3 +42,16 @@ func TestTranspose(t *testing.T) {
}, },
) )
} }
func TestPairwise(t *testing.T) {
is := is.New(t)
is.Equal(
aoc.Pairwise([]int{1, 2, 3, 4}),
[][2]int{
{1, 2},
{2, 3},
{3, 4},
},
)
}

View File

View File

@ -31,3 +31,21 @@ func TestABS(t *testing.T) {
is.Equal(aoc.ABS(0), 0) is.Equal(aoc.ABS(0), 0)
is.Equal(aoc.ABS(-1), 1) is.Equal(aoc.ABS(-1), 1)
} }
func TestMin(t *testing.T) {
is := is.New(t)
is.Equal(aoc.Min(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1)
}
func TestMax(t *testing.T) {
is := is.New(t)
is.Equal(aoc.Max(1, 10, 2, 3, 4, 5, 6, 7, 8, 9, 1), 10)
}
func TestSum(t *testing.T) {
is := is.New(t)
is.Equal(aoc.Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 55)
}

12
set.go
View File

@ -2,22 +2,22 @@ package aoc
import "golang.org/x/exp/maps" import "golang.org/x/exp/maps"
type set[T comparable] map[T]struct{} type Set[T comparable] map[T]struct{}
func Set[T comparable](arr ...T) set[T] { func NewSet[T comparable](arr ...T) Set[T] {
m := make(set[T], len(arr)) m := make(Set[T], len(arr))
for _, a := range arr { for _, a := range arr {
m[a] = struct{}{} m[a] = struct{}{}
} }
return m return m
} }
func (m *set[T]) Add(a T) { func (m *Set[T]) Add(a T) {
(*m)[a] = struct{}{} (*m)[a] = struct{}{}
} }
func (m *set[T]) Items() []T { func (m *Set[T]) Items() []T {
return maps.Keys(*m) return maps.Keys(*m)
} }
func (m *set[T]) Has(a T) bool { func (m *Set[T]) Has(a T) bool {
var ok bool var ok bool
_, ok = (*m)[a] _, ok = (*m)[a]
return ok return ok

View File

@ -11,7 +11,7 @@ import (
func TestSet(t *testing.T) { func TestSet(t *testing.T) {
is := is.New(t) is := is.New(t)
s := aoc.Set(1, 2, 3) s := aoc.NewSet(1, 2, 3)
is.True(!s.Has(0)) is.True(!s.Has(0))
is.True(s.Has(1)) is.True(s.Has(1))
is.True(s.Has(2)) is.True(s.Has(2))
@ -24,4 +24,6 @@ func TestSet(t *testing.T) {
items := s.Items() items := s.Items()
sort.Ints(items) sort.Ints(items)
is.Equal(items, []int{1, 2, 3, 4}) is.Equal(items, []int{1, 2, 3, 4})
is.True(aoc.In(4, items...))
is.True(!aoc.In(99, items...))
} }

64
stack.go Normal file
View File

@ -0,0 +1,64 @@
package aoc
type stack[T any] []T
func NewStack[T any](items ...T) *stack[T] {
s := make(stack[T], len(items))
copy(s, items)
return &s
}
// Push adds an element to the top of the stack.
func (s *stack[T]) Push(v T) {
*s = append(*s, v)
}
// Pop removes the top element from the stack and returns it.
// If the stack is empty, it panics.
func (s *stack[T]) Pop() T {
if s.IsEmpty() {
var zero T
return zero
}
v := (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
return v
}
// Peek returns the top element from the stack without removing it.
// If the stack is empty, it panics.
func (s *stack[T]) Peek() T {
if s.IsEmpty() {
var zero T
return zero
}
return (*s)[len(*s)-1]
}
// Len returns the number of elements in the stack.
func (s *stack[T]) Len() int {
return len(*s)
}
// IsEmpty returns true if the stack is empty and false otherwise.
func (s *stack[T]) IsEmpty() bool {
return s == nil || len(*s) == 0
}
// Clear removes all elements from the stack, returning it to its initial state.
func (s *stack[T]) Clear() {
*s = (*s)[:0]
}
func (s *stack[T]) Pull() T {
if s.IsEmpty() {
var zero T
return zero
}
v := (*s)[0]
*s = (*s)[1:]
return v
}