Update go version

This commit is contained in:
dwrz
2026-08-21 10:23:37 +00:00
parent 78248a6145
commit c2e2d9ea02
466 changed files with 67766 additions and 2881 deletions

View File

@@ -0,0 +1,24 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
// AllPaths returns the set of nodes that are part of at least one path from src to dst.
func AllPaths[NodeID comparable](g Graph[NodeID], src, dst NodeID) map[NodeID]bool {
// We intersect the forward closure of 'src' with
// the reverse closure of 'dst'. This is not the most
// efficient implementation, but it's the clearest,
// and the previous one had bugs.
fwd := Reachable(g, src)
rev := Reachable(Transpose(g), dst)
// Intersection
for n := range fwd {
if !rev[n] {
delete(fwd, n)
}
}
return fwd
}

View File

@@ -0,0 +1,89 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import "iter"
// A CompactGraph is a Graph with nodes that are compactly numbered from [0,
// NumNodes()).
//
// Compactly numbered graphs are useful for many graph algorithms, and many
// graph representations are naturally compact.
//
// To compact an arbitrary graph, use [Compact].
type CompactGraph interface {
Graph[int]
IsCompact()
}
// A nodePreserving graph is a transformation of another graph that preserves
// node IDs.
type nodePreserving interface {
Graph[int]
unwrapPreservingNodes() Graph[int]
}
// Compact takes a Graph with arbitrary NodeIDs and returns a compact graph.
//
// If g implements [CompactGraph], it assumes g is already compact and simply
// returns g and an identity mapping.
func Compact[NodeID comparable](g Graph[NodeID]) (CompactGraph, *Index[NodeID]) {
// If it's already compact, simply return it.
if gc, ok := g.(CompactGraph); ok {
// The above assertion ensures NodeID is int, so we know this type
// assertion will always succeed.
return gc, any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID])
}
// If it's a transformation and the underlying graph is compact, we can use
// an identity index. Though we still need to build a compactGraph to
// satisfy the CompactGraph interface.
g2, _ := g.(nodePreserving)
for g2 != nil {
unwrapped := g2.unwrapPreservingNodes()
if gc, ok := unwrapped.(CompactGraph); ok {
index := any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID])
cg := compactGraph[NodeID]{g, index}
return &cg, index
}
g2, _ = unwrapped.(nodePreserving)
}
// Nope, just build an index.
cg := compactGraph[NodeID]{g, NewIndex(g.Nodes())}
return &cg, cg.m
}
type compactGraph[NodeID comparable] struct {
g Graph[NodeID]
m *Index[NodeID]
}
func (g *compactGraph[NodeID]) Nodes() iter.Seq[int] {
return func(yield func(int) bool) {
for i := range g.g.NumNodes() {
if !yield(i) {
break
}
}
}
}
func (g *compactGraph[NodeID]) NumNodes() int {
return g.g.NumNodes()
}
func (g *compactGraph[NodeID]) Out(node int) iter.Seq[int] {
id := g.m.Value(node)
return func(yield func(int) bool) {
for nid := range g.g.Out(id) {
if !yield(g.m.Index(nid)) {
break
}
}
}
}
func (g *compactGraph[NodeID]) IsCompact() {}

View File

@@ -0,0 +1,33 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package graph provides a common abstraction for directed graphs and standard
// graph algorithms.
//
// In general, this package does not provide or assume any concrete graph
// representation. It's up to the caller of this package to implement the
// [Graph] interface, either directly or as an adapter around another type.
package graph
import "iter"
// A Graph implements a directed graph where nodes in the graph are identified
// by the NodeID type.
//
// If a concrete graph type stores additional information about nodes and/or
// edges, it will conventionally provide methods of the form:
//
// Node(node NodeID) nodeInfo
// Edge(from, to NodeID) edgeInfo
type Graph[NodeID comparable] interface {
// Nodes yields all nodes in this graph.
Nodes() iter.Seq[NodeID]
// NumNodes returns the total number of nodes in this graph.
NumNodes() int
// Out yields the out-edges of node. Out must be deterministic, though
// otherwise there is no constraint on the order of the returned sequence.
Out(node NodeID) iter.Seq[NodeID]
}

View File

@@ -0,0 +1,96 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import (
"fmt"
"iter"
"slices"
)
// An Index is an immutable, bijective map between [0, N) and an ordered list of keys.
type Index[Key comparable] struct {
// There are three Index representations:
//
// - If identN > 0, an identity map of [0, identN).
// - If index == nil, a sorted integer index in values.
// - Otherwise, a full index in values and index.
identN int
values []Key
index map[Key]int
}
// NewIndex returns an index for the specified list of values.
func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] {
vs := slices.Collect(values)
if len(vs) == 0 {
return new(Index[Key])
}
// Fast path: a naturally sorted list needs no index. (Sadly, there's no way
// to ask "is Key ordered?")
if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) {
return &Index[Key]{values: vs}
}
index := make(map[Key]int, len(vs))
for i, v := range vs {
index[v] = i
}
return &Index[Key]{values: vs, index: index}
}
// NewIdentityIndex returns an index that maps [0, n) to [0, n).
func NewIdentityIndex(n int) *Index[int] {
if n < 0 {
panic("n < 0")
}
// If n == 0, this is actually a "sorted integer index", but it doesn't
// matter because everything is out of bounds either way.
return &Index[int]{identN: n}
}
// Value maps an index to a key.
func (ix *Index[Key]) Value(index int) Key {
if ix.identN > 0 {
if index < 0 || index >= ix.identN {
panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN))
}
return any(index).(Key)
}
if index < 0 || index >= len(ix.values) {
panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN))
}
return ix.values[index]
}
// Index maps a key to an index.
func (ix *Index[Key]) Index(key Key) int {
if key, ok := any(key).(int); ok {
// Integer-only optimizations.
switch {
case ix.identN > 0:
// Identity.
if 0 <= key && key < ix.identN {
return key
}
goto oob
case ix.index == nil:
// Sorted integers.
if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok {
return i
}
goto oob
}
}
if i, ok := ix.index[key]; ok {
return i
}
oob:
panic(fmt.Sprintf("key %v not in index", key))
}

View File

@@ -0,0 +1,94 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import "slices"
// Postorder returns the sequence of nodes in the spanning DAG of g, in
// postorder.
//
// For rootless subgraphs, it breaks cycles by starting at the lowest numbered
// node.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID {
cg, nodeMap := Compact(g)
numNodes := cg.NumNodes()
if numNodes == 0 {
return nil
}
result := make([]NodeID, 0, numNodes)
visited := newBitset(numNodes)
onStack := newBitset(numNodes)
// visit performs a Depth-First Search.
var visit func(u int)
visit = func(u int) {
if !visited.add(u) {
return
}
onStack.add(u)
for v := range cg.Out(u) {
if onStack.contains(v) {
// Cycle detected (back-edge).
// To resolve, we simply skip processing this edge further in the
// current recursion, effectively "breaking" the cycle at this point.
continue
}
visit(v)
}
onStack.remove(u)
// Post-order: add to result after all descendants are processed.
result = append(result, nodeMap.Value(u))
}
// Visit every node in ascending order to ensure stability.
for u := range numNodes {
visit(u)
}
return result
}
// ReversePostorder returns the nodes of the graph in reverse post-order.
//
// If g is a directed acyclic graph (DAG), the result is a topological sort of
// g.
//
// See [Postorder] for how this handles back-edges and cycles.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID {
result := Postorder(g)
slices.Reverse(result)
return result
}
// bitset is a simple fixed-size bitset used to reduce memory overhead.
type bitset []uint64
func newBitset(n int) bitset {
return make(bitset, (n+63)/64)
}
func (b bitset) add(u int) bool {
if b.contains(u) {
return false
}
b[u/64] |= 1 << (u % 64)
return true
}
func (b bitset) remove(u int) {
b[u/64] &= ^(1 << (u % 64))
}
func (b bitset) contains(u int) bool {
return b[u/64]&(1<<(u%64)) != 0
}

View File

@@ -0,0 +1,23 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
// Reachable returns the set of nodes reachable from the given roots.
func Reachable[NodeID comparable](g Graph[NodeID], roots ...NodeID) map[NodeID]bool {
seen := make(map[NodeID]bool)
var visit func(node NodeID)
visit = func(node NodeID) {
if !seen[node] {
seen[node] = true
for e := range g.Out(node) {
visit(e)
}
}
}
for _, root := range roots {
visit(root)
}
return seen
}

View File

@@ -0,0 +1,39 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import "slices"
// SCCs computes the strongly connected components of the graph g.
func SCCs[NodeID comparable](g Graph[NodeID]) [][]NodeID {
// Use Kosaraju's algorithm. Tarjan is overkill here.
// Forward pass
S := Postorder(g)
// Reverse pass
gt := Transpose(g)
seen := make(map[NodeID]bool)
var scc []NodeID
var sccs [][]NodeID
var rvisit func(NodeID)
rvisit = func(u NodeID) {
if !seen[u] {
seen[u] = true
scc = append(scc, u)
for v := range gt.Out(u) {
rvisit(v)
}
}
}
for _, root := range slices.Backward(S) {
if !seen[root] {
scc = nil
rvisit(root)
sccs = append(sccs, scc)
}
}
return sccs
}

View File

@@ -0,0 +1,45 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import "slices"
// ShortestPath returns a shortest path from src to dst in g.
// It returns the path as a slice of nodes starting with src and ending with dst.
// If no path is found, it returns nil.
func ShortestPath[NodeID comparable](g Graph[NodeID], src, dst NodeID) []NodeID {
if src == dst {
return []NodeID{src}
}
pred := make(map[NodeID]NodeID)
queue := []NodeID{src}
// Mark src as seen.
pred[src] = src
for len(queue) > 0 {
n := queue[0]
queue = queue[1:]
if n == dst {
// Reconstruct path
var path []NodeID
for curr := dst; curr != src; curr = pred[curr] {
path = append(path, curr)
}
path = append(path, src)
slices.Reverse(path)
return path
}
for v := range g.Out(n) {
if _, seen := pred[v]; !seen {
pred[v] = n
queue = append(queue, v)
}
}
}
return nil
}

View File

@@ -0,0 +1,51 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
import (
"iter"
"slices"
)
type transpose[NodeID comparable] struct {
Graph Graph[NodeID]
preds map[NodeID][]NodeID
}
// transpose returns a graph like g but with all edges reversed. Node IDs are
// identical to the underlying graph.
//
// Transpose preserves compactness.
func Transpose[NodeID comparable](g Graph[NodeID]) Graph[NodeID] {
if g, ok := g.(transpose[NodeID]); ok {
// Transpose(Transpose(g)) == g
return g.Graph
}
preds := make(map[NodeID][]NodeID)
for nid := range g.Nodes() {
for succ := range g.Out(nid) {
preds[succ] = append(preds[succ], nid)
}
}
return transpose[NodeID]{g, preds}
}
func (t transpose[NodeID]) NumNodes() int {
return len(t.preds)
}
func (t transpose[NodeID]) Nodes() iter.Seq[NodeID] {
return t.Graph.Nodes()
}
func (t transpose[NodeID]) Out(n NodeID) iter.Seq[NodeID] {
return slices.Values(t.preds[n])
}
//lint:ignore U1000 False positive in Staticcheck 2026.1 and older.
func (t transpose[NodeID]) unwrapPreservingNodes() Graph[NodeID] {
return t.Graph
}