package aoc import ( "cmp" "fmt" "iter" "sort" "strings" "golang.org/x/exp/maps" ) type Vector struct { Offset Point[int] Scale int } func (v Vector) Point() Point[int] { return v.Offset.Scale(v.Scale) } type Point[T integer] [2]T func (p Point[T]) Add(a Point[T]) Point[T] { return Point[T]{p[0] + a[0], p[1] + a[1]} } func (p Point[T]) Scale(m T) Point[T] { return Point[T]{p[0] * m, p[1] * m} } func (p Point[T]) Less(b Point[T]) bool { if p[0] != b[0] { return p[0] < b[0] } return p[1] < b[1] } func Transpose[T any](matrix [][]T) [][]T { rows, cols := len(matrix), len(matrix[0]) m := make([][]T, cols) for i := range m { m[i] = make([]T, rows) } for i := 0; i < cols; i++ { for j := 0; j < rows; j++ { m[i][j] = matrix[j][i] } } return m } // NumPoints the number of the points inside an outline plus the number of points in the outline func NumPoints(outline []Point[int], 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 } area := sum / 2 // pick's theorem - find the number of points in a shape given its area return (ABS(area) - borderLength/2 + 1) + borderLength } type Map[I integer, T any] [][]T func (m *Map[I, T]) Get(p Point[I]) (Point[I], T, bool) { var zero T if !m.Valid(p) { return [2]I{0, 0}, zero, false } return p, (*m)[p[0]][p[1]], true } func (m *Map[I, T]) Size() (I, I) { if m == nil || len(*m) == 0 { return 0, 0 } return I(len(*m)), I(len((*m)[0])) } func (m *Map[I, T]) Valid(p Point[I]) bool { rows, cols := m.Size() return p[0] >= 0 && p[0] < rows && p[1] >= 0 && p[1] < cols } type cmap[C number, N comparable] struct { base pather[C, N] neighbors map[N]map[N]C } func (m *cmap[C, N]) Cost(a, b N) C { if v, ok := m.neighbors[a]; ok { return v[b] } return 0 } func (m *cmap[C, N]) Neighbors(n N) []N { if v, ok := m.neighbors[n]; ok { return maps.Keys(v) } return nil } func (m *cmap[C, N]) Target(n N, c C) bool { return m.base.Target(n, c) } func (m *cmap[C, N]) String() string { var b = &strings.Builder{} for k, nbs := range m.neighbors { fmt.Fprintln(b, k) for to, c := range nbs { fmt.Fprintln(b, " ", to, c) } } return b.String() } func CompressMap[C number, N comparable](p pather[C, N], start N) pather[C, N] { var next = []N{start} var visited = make(map[N]map[N]C) var n N for len(next) > 0 { n, next = next[len(next)-1], next[:len(next)-1] if _, ok := visited[n]; ok { continue } nbs := p.Neighbors(n) if len(nbs) == 2 { a, b := nbs[0], nbs[1] if to, ok := visited[a]; ok { to[b] = to[n] + p.Cost(n, b) delete(to, n) visited[a] = to } else if to, ok := visited[b]; ok { to[a] = to[n] + p.Cost(n, a) delete(to, n) visited[b] = to } continue } visited[n] = make(map[N]C) next = append(next, nbs...) for _, to := range nbs { visited[n][to] = p.Cost(n, to) } } return &cmap[C, N]{base: p, neighbors: visited} } 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] } func (v *vertex[V, W]) Neighbors() []V { var nbs []V sort.Sort(v.Edges) for _, e := range v.Edges { nbs = append(nbs, e.Vertex.Value) } return nbs } type edge[V comparable, W cmp.Ordered] struct { Vertex *vertex[V, W] Weight 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 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]) AddVertex(id V, value V) { (*g)[id] = &vertex[V, W]{Value: value} } func (g *graph[V, W]) AddEdge(from, to V, w W) { if g == nil { return } if _, ok := (*g)[from]; !ok { return } if _, ok := (*g)[to]; !ok { return } (*g)[from].Edges = append((*g)[from].Edges, edge[V, W]{(*g)[to], w}) } func (g *graph[V, W]) Neighbors(v V) []V { if g == nil { return nil } return (*g)[v].Neighbors() } func (g *graph[V, W]) AdjacencyList() adjacencyList[V] { m := make(map[V][]V) for id, v := range *g { if len(v.Edges) == 0 { continue } m[id] = v.Neighbors() } return m } func WithAdjacencyList[W cmp.Ordered, V comparable](list adjacencyList[V]) graphOption[V, W] { var zeroW W return func(g *graph[V, W]) { for vertex, edges := range list { if _, ok := (*g)[vertex]; !ok { g.AddVertex(vertex, vertex) } // add edges to vertex for _, edge := range edges { // add edge as vertex, if not added if _, ok := (*g)[edge]; !ok { g.AddVertex(edge, edge) } g.AddEdge(vertex, edge, zeroW) // no weights in this adjacency list } } } } 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) } } } } }