From e046a6c06d2dc63125c3368ea1787d80a7aa8202 Mon Sep 17 00:00:00 2001 From: xuu Date: Wed, 30 Oct 2024 13:32:44 -0600 Subject: [PATCH] chore: cleanup and add tools --- aoc2023/day21/main.go | 79 ++++++++++++++++++++++++++++++++++--- aoc2023/day21/main_test.go | 8 ++-- aoc_test.go | 24 +---------- grids.go | 81 +++++++++++++++++++++++++++++++------- grids_test.go | 75 +++++++++++++++++++++++++++++++++++ itertools_test.go | 13 ++++++ lists.go => list.go | 0 math_test.go | 18 +++++++++ set.go | 12 +++--- set_test.go | 4 +- stack.go | 64 ++++++++++++++++++++++++++++++ 11 files changed, 325 insertions(+), 53 deletions(-) create mode 100644 grids_test.go rename lists.go => list.go (100%) create mode 100644 stack.go diff --git a/aoc2023/day21/main.go b/aoc2023/day21/main.go index c0596d8..8613425 100644 --- a/aoc2023/day21/main.go +++ b/aoc2023/day21/main.go @@ -10,7 +10,7 @@ import ( // var log = aoc.Log -func main() { aoc.MustResult(aoc.Runner(run)) } +func main() { aoc.MustResult(aoc.Runner(runner(64))) } type result struct { valuePT1 int @@ -19,12 +19,81 @@ type result struct { 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() { - _ = scan.Text() + for scan.Scan() { + 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 + } +} + +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)) } - return &result{}, nil + 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) +} \ No newline at end of file diff --git a/aoc2023/day21/main_test.go b/aoc2023/day21/main_test.go index b55d509..461d2ab 100644 --- a/aoc2023/day21/main_test.go +++ b/aoc2023/day21/main_test.go @@ -20,11 +20,11 @@ func TestExample(t *testing.T) { is := is.New(t) scan := bufio.NewScanner(bytes.NewReader(example)) - result, err := run(scan) + result, err := runner(6)(scan) is.NoErr(err) t.Log(result) - is.Equal(result.valuePT1, 0) + is.Equal(result.valuePT1, 16) is.Equal(result.valuePT2, 0) } @@ -32,10 +32,10 @@ func TestSolution(t *testing.T) { is := is.New(t) scan := bufio.NewScanner(bytes.NewReader(input)) - result, err := run(scan) + result, err := runner(64)(scan) is.NoErr(err) t.Log(result) - is.Equal(result.valuePT1, 0) + is.Equal(result.valuePT1, 3709) is.Equal(result.valuePT2, 0) } diff --git a/aoc_test.go b/aoc_test.go index c723dd2..b5d471b 100644 --- a/aoc_test.go +++ b/aoc_test.go @@ -9,7 +9,6 @@ import ( aoc "go.sour.is/advent-of-code" ) - func TestList(t *testing.T) { is := is.New(t) @@ -56,7 +55,6 @@ func TestPriorityQueue(t *testing.T) { is.True(v == nil) } - func ExamplePriorityQueue() { type memo struct { pt int @@ -74,7 +72,7 @@ func ExamplePriorityQueue() { } pq := aoc.PriorityQueue(less) - visited := aoc.Set([]int{}...) + visited := aoc.NewSet([]int{}...) dist := aoc.DefaultMap[int](int(^uint(0) >> 1)) dist.Set(0, 0) @@ -117,25 +115,7 @@ func ExamplePriorityQueue() { // point 5 is 22 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() { type memo struct { @@ -154,7 +134,7 @@ func ExampleFibHeap() { } pq := aoc.FibHeap(less) - visited := aoc.Set([]int{}...) + visited := aoc.NewSet([]int{}...) dist := aoc.DefaultMap[int](int(^uint(0) >> 1)) dist.Set(0, 0) diff --git a/grids.go b/grids.go index a07340c..cf0b893 100644 --- a/grids.go +++ b/grids.go @@ -3,6 +3,7 @@ package aoc import ( "cmp" "fmt" + "iter" "sort" "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} } -type adjacencyList[V any, C comparable] map[C][]V -type graph[V any, W cmp.Ordered, C comparable] map[C]*vertex[V, W] -type graphOption[V any, W cmp.Ordered, C comparable] func(g *graph[V, W, C]) -type vertex[V any, W cmp.Ordered] struct { +type adjacencyList[V comparable] map[V][]V +type graph[V comparable, W cmp.Ordered] map[V]*vertex[V, W] +type graphOption[V comparable, W cmp.Ordered] func(g *graph[V, W]) +type vertex[V comparable, W cmp.Ordered] struct { Value V Edges edges[V, W] } @@ -173,27 +174,27 @@ func (v *vertex[V, W]) Neighbors() []V { return nbs } -type edge[V any, W cmp.Ordered] struct { +type edge[V comparable, W cmp.Ordered] struct { Vertex *vertex[V, 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]) 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 Graph[V any, W cmp.Ordered, C comparable](opts ...graphOption[V, W, C]) *graph[V, W, C] { - g := make(graph[V, W, C]) +func Graph[V comparable, W cmp.Ordered](opts ...graphOption[V, W]) *graph[V, W] { + g := make(graph[V, W]) for _, opt := range opts { opt(&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} } -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 { 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}) } -func (g *graph[V, W, C]) Neighbors(v C) []V { +func (g *graph[V, W]) Neighbors(v V) []V { if g == nil { return nil } return (*g)[v].Neighbors() } -func (g *graph[V, W, C]) AdjacencyList() adjacencyList[V, C] { - m := make(map[C][]V) +func (g *graph[V, W]) AdjacencyList() adjacencyList[V] { + m := make(map[V][]V) for id, v := range *g { if len(v.Edges) == 0 { continue @@ -224,9 +225,9 @@ func (g *graph[V, W, C]) AdjacencyList() adjacencyList[V, C] { 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 - return func(g *graph[C, W, C]) { + return func(g *graph[V, W]) { for vertex, edges := range list { if _, ok := (*g)[vertex]; !ok { 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) + } + } + } + } +} \ No newline at end of file diff --git a/grids_test.go b/grids_test.go new file mode 100644 index 0000000..2289d87 --- /dev/null +++ b/grids_test.go @@ -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 +} diff --git a/itertools_test.go b/itertools_test.go index 63d1b45..a76af63 100644 --- a/itertools_test.go +++ b/itertools_test.go @@ -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}, + }, + ) +} \ No newline at end of file diff --git a/lists.go b/list.go similarity index 100% rename from lists.go rename to list.go diff --git a/math_test.go b/math_test.go index eeb8f2f..6681b44 100644 --- a/math_test.go +++ b/math_test.go @@ -31,3 +31,21 @@ func TestABS(t *testing.T) { is.Equal(aoc.ABS(0), 0) 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) +} \ No newline at end of file diff --git a/set.go b/set.go index fc6939e..f5fe176 100644 --- a/set.go +++ b/set.go @@ -2,22 +2,22 @@ package aoc 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] { - m := make(set[T], len(arr)) +func NewSet[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) { +func (m *Set[T]) Add(a T) { (*m)[a] = struct{}{} } -func (m *set[T]) Items() []T { +func (m *Set[T]) Items() []T { return maps.Keys(*m) } -func (m *set[T]) Has(a T) bool { +func (m *Set[T]) Has(a T) bool { var ok bool _, ok = (*m)[a] return ok diff --git a/set_test.go b/set_test.go index c4a5d3d..b99bf22 100644 --- a/set_test.go +++ b/set_test.go @@ -11,7 +11,7 @@ import ( func TestSet(t *testing.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(1)) is.True(s.Has(2)) @@ -24,4 +24,6 @@ func TestSet(t *testing.T) { items := s.Items() sort.Ints(items) is.Equal(items, []int{1, 2, 3, 4}) + is.True(aoc.In(4, items...)) + is.True(!aoc.In(99, items...)) } diff --git a/stack.go b/stack.go new file mode 100644 index 0000000..4ecfb6c --- /dev/null +++ b/stack.go @@ -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 +} +