Documentation
¶
Overview ¶
Package traverse visits the vertices of a gograph.Graph in breadth-first, depth-first, closest-first, topological and random walk order.
Each traversal except the random walk comes in two forms. An iterator, such as the one NewBreadthFirstIterator returns, hands out one vertex per Next call and can be Reset. A sequence, such as the one BFS returns, is for a range loop. BFS, BFSWithDepth, DFS and ClosestFirst do their work as the loop asks for the next vertex, so breaking out of the loop ends the traversal there, and ranging over the sequence again runs the traversal again from the start. Topological sorts the graph when it is called.
DFSEvents and DFSEventsAll report each step of a depth-first search: when a vertex is discovered and finished, and whether each edge is a tree, back, forward or cross edge. Algorithms such as cycle finding and topological sort by finish time are a loop over these events.
Each vertex is the graph's own vertex, the one GetVertexByID returns for its label. Changing the graph during a traversal isn't supported, with one exception: a vertex removed from the graph after the traversal queued it is skipped.
Index ¶
- func BFS[T comparable](g gograph.Graph[T], start T) iter.Seq[*gograph.Vertex[T]]
- func BFSWithDepth[T comparable](g gograph.Graph[T], start T) iter.Seq2[*gograph.Vertex[T], int]
- func ClosestFirst[T comparable](g gograph.Graph[T], start T) iter.Seq2[*gograph.Vertex[T], float64]
- func DFS[T comparable](g gograph.Graph[T], start T) iter.Seq[*gograph.Vertex[T]]
- func DFSEvents[T comparable](g gograph.Graph[T], start T) iter.Seq[DFSEvent[T]]
- func DFSEventsAll[T comparable](g gograph.Graph[T]) iter.Seq[DFSEvent[T]]
- func Topological[T comparable](g gograph.Graph[T]) (iter.Seq[*gograph.Vertex[T]], error)
- type DFSEvent
- type DFSEventKind
- type Iterator
- func NewBreadthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error)
- func NewClosestFirstIterator[T comparable](graph gograph.Graph[T], start T) (Iterator[T], error)
- func NewDepthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error)
- func NewRandomWalkIterator[T comparable](graph gograph.Graph[T], start T, steps int) (Iterator[T], error)
- func NewTopologicalIterator[T comparable](g gograph.Graph[T]) (Iterator[T], error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BFS ¶ added in v0.10.0
BFS returns the vertices that start reaches, in breadth-first order: the order of NewBreadthFirstIterator. It yields nothing if start isn't in g, and skips a vertex removed from g after it was queued.
Ranging over all of it takes O(V+E) time and O(V) extra space for the V vertices and E edges that start reaches.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Directed())
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("B"))
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("C"))
_, _ = g.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("D"))
_, _ = g.AddEdge(gograph.NewVertex("C"), gograph.NewVertex("E"))
for v := range traverse.BFS(g, "A") {
fmt.Println(v.Label())
if v.Label() == "C" {
break
}
}
}
Output: A B C
func BFSWithDepth ¶ added in v0.10.0
BFSWithDepth returns the vertices that start reaches, in the order of BFS, each with its depth. start has depth 0, and every other vertex the number of edges on a shortest path from start. It yields nothing if start isn't in g, and skips a vertex removed from g after it was queued.
Ranging over all of it takes O(V+E) time and O(V) extra space for the V vertices and E edges that start reaches.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Directed())
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("B"))
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("C"))
_, _ = g.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("D"))
_, _ = g.AddEdge(gograph.NewVertex("C"), gograph.NewVertex("D"))
for v, depth := range traverse.BFSWithDepth(g, "A") {
fmt.Println(v.Label(), depth)
}
}
Output: A 0 B 1 C 1 D 2
func ClosestFirst ¶ added in v0.10.0
ClosestFirst returns the vertices that start reaches, by increasing distance from start: the order of NewClosestFirstIterator. Each vertex comes with its distance, the sum of the edge weights on a shortest path from start. It yields nothing if start isn't in g, and skips a vertex removed from g after it was queued.
Edge weights are expected to be non-negative. An edge with a NaN weight is skipped, and a vertex reached only through a +Inf weight comes after every vertex at a finite distance. An edge added without a weight weighs 0, so in a graph without weights every distance is 0. BFSWithDepth gives the number of edges from start instead.
Ranging over all of it takes O((V+E) log V) time and O(V) extra space for the V vertices and E edges that start reaches.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Directed(), gograph.Weighted())
_, _ = g.AddEdge(gograph.NewVertex("home"), gograph.NewVertex("school"), gograph.WithEdgeWeight(4))
_, _ = g.AddEdge(gograph.NewVertex("home"), gograph.NewVertex("bakery"), gograph.WithEdgeWeight(1))
_, _ = g.AddEdge(gograph.NewVertex("bakery"), gograph.NewVertex("school"), gograph.WithEdgeWeight(2))
for v, dist := range traverse.ClosestFirst(g, "home") {
fmt.Println(v.Label(), dist)
}
}
Output: home 0 bakery 1 school 3
func DFS ¶ added in v0.10.0
DFS returns the vertices that start reaches, in the order of NewDepthFirstIterator: the next vertex is always the latest one found, and the neighbors of a vertex are found in the order of Neighbors. It yields nothing if start isn't in g, and skips a vertex removed from g after it was found.
Ranging over all of it takes O(V+E) time and O(V) extra space for the V vertices and E edges that start reaches.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Directed())
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("B"))
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("C"))
_, _ = g.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("D"))
// the latest vertex found comes next, so C comes before B
for v := range traverse.DFS(g, "A") {
fmt.Println(v.Label())
}
}
Output: A C B D
func DFSEvents ¶ added in v0.10.0
DFSEvents returns the events of a depth-first search from start. It discovers a vertex when it first enters it, then looks at its neighbors in the order of Neighbors, one edge event each, and finishes the vertex once all of them are handled. A tree edge discovers its destination right away, so the events of each vertex nest inside the events of its parent, like parentheses. A missing start yields nothing.
In a directed graph, each edge from a vertex the search reaches comes once, as one of the four edge kinds. A forward edge goes to a finished vertex discovered after the edge's source, and a cross edge to one discovered before it. In an undirected graph, each edge comes once too: as a tree edge when it discovers a vertex, or as a back edge from the deeper of its two vertices. The search skips the edge back to the parent and the second direction of an edge it already reported, so only tree and back edges occur. A self-loop is a back edge in both kinds of graph.
The search runs as the loop asks for the next event, so breaking out of the loop ends it there, and ranging over the sequence again searches again. It keeps its own stack, so the goroutine stack stays small on a long path. A vertex or an edge removed from g during the loop is skipped when the search gets to it.
Ranging over all of it takes O(V+E) time for the V vertices and E edges that start reaches. It keeps O(V) state, plus the neighbors still to look at of each vertex on the current path, which is at most O(V+E).
Example ¶
package main
import (
"fmt"
"strings"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Directed())
a := g.AddVertexByLabel("A")
b := g.AddVertexByLabel("B")
c := g.AddVertexByLabel("C")
_, _ = g.AddEdge(a, b)
_, _ = g.AddEdge(a, c)
_, _ = g.AddEdge(b, c)
_, _ = g.AddEdge(c, a)
for e := range traverse.DFSEvents(g, "A") {
indent := strings.Repeat(" ", e.Depth)
if e.Edge != nil {
fmt.Printf("%s%v %s -> %s\n", indent, e.Kind, e.Edge.Source().Label(), e.Edge.Destination().Label())
continue
}
fmt.Printf("%s%v %s\n", indent, e.Kind, e.Vertex.Label())
}
}
Output: DiscoverVertex A TreeEdge A -> B DiscoverVertex B TreeEdge B -> C DiscoverVertex C BackEdge C -> A FinishVertex C FinishVertex B ForwardEdge A -> C FinishVertex A
func DFSEventsAll ¶ added in v0.10.0
DFSEventsAll returns the events of a depth-first search over all of g. It searches from each vertex not discovered yet, in the order of GetAllVertices, and each of those vertices has depth 0. The events follow the rules of DFSEvents, and a cross edge can go to a vertex of an earlier search.
Ranging over all of it takes O(V+E) time. It keeps O(V) state, plus the neighbors still to look at of each vertex on the current path, which is at most O(V+E).
Example ¶
package main
import (
"fmt"
"slices"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
// in a graph without cycles, the reverse of the finish order is a
// topological order
g := gograph.New[string](gograph.Directed())
for _, e := range [][2]string{
{"shirt", "tie"}, {"tie", "jacket"}, {"pants", "shoes"}, {"pants", "belt"}, {"belt", "jacket"},
} {
_, _ = g.AddEdge(gograph.NewVertex(e[0]), gograph.NewVertex(e[1]))
}
var order []string
for e := range traverse.DFSEventsAll(g) {
if e.Kind == traverse.FinishVertex {
order = append(order, e.Vertex.Label())
}
}
slices.Reverse(order)
fmt.Println(order)
}
Output: [pants belt shoes shirt tie jacket]
func Topological ¶ added in v0.10.0
Topological returns the vertices of g in the order of gograph.TopologySort. It returns gograph.ErrNotDirected if g isn't directed and gograph.ErrDAGHasCycle if g has a cycle, each with a sequence that yields nothing.
It sorts g when it is called, in O(V+E) time and O(V) space, so breaking out of the loop early saves nothing, and ranging over the sequence again yields the same order even if g has changed.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/traverse"
)
func main() {
g := gograph.New[string](gograph.Acyclic())
_, _ = g.AddEdge(gograph.NewVertex("fetch"), gograph.NewVertex("build"))
_, _ = g.AddEdge(gograph.NewVertex("build"), gograph.NewVertex("test"))
_, _ = g.AddEdge(gograph.NewVertex("fetch"), gograph.NewVertex("lint"))
steps, err := traverse.Topological(g)
if err != nil {
fmt.Println(err)
return
}
for v := range steps {
fmt.Println(v.Label())
}
}
Output: fetch build lint test
Types ¶
type DFSEvent ¶ added in v0.10.0
type DFSEvent[T comparable] struct { Kind DFSEventKind // Vertex is set for DiscoverVertex and FinishVertex. Vertex *gograph.Vertex[T] // Edge is set for the edge kinds. Its source is the vertex the search is // at, and its destination the neighbor the search looks at. Edge *gograph.Edge[T] // Depth is the depth of Vertex, or of the source of Edge, in the // depth-first tree. The vertex a search starts from has depth 0. Depth int }
DFSEvent is one step of a depth-first search.
type DFSEventKind ¶ added in v0.10.0
type DFSEventKind int
DFSEventKind tells which step of a depth-first search a DFSEvent reports.
const ( // DiscoverVertex is the first time the search reaches a vertex. DiscoverVertex DFSEventKind = iota // FinishVertex comes once every vertex reachable from the vertex has been // handled. FinishVertex // TreeEdge is an edge to an undiscovered vertex. The tree edges form the // depth-first tree, and the source of each one is the parent of its // destination. TreeEdge // BackEdge is an edge to a vertex still on the current path, which closes // a cycle. A self-loop is a back edge. BackEdge // ForwardEdge is an edge to a finished descendant. It occurs in directed // graphs only. ForwardEdge // CrossEdge is an edge to a finished vertex that isn't a descendant. It // occurs in directed graphs only. CrossEdge )
func (DFSEventKind) String ¶ added in v0.10.0
func (k DFSEventKind) String() string
String returns the name of the kind, such as "TreeEdge", or "DFSEventKind(n)" for a value outside the constants.
type Iterator ¶
type Iterator[T comparable] interface { // HasNext returns a boolean value indicating whether there are more // elements to be iterated over. It returns true if there are more // elements. Otherwise, returns false. HasNext() bool // Next returns the next element in the sequence being iterated over. // If there are no more elements, it returns nil. It also advances // the iterator to the next element. Next() *gograph.Vertex[T] // Iterate iterates over all elements in the sequence and calls the // provided callback function on each element. The callback function // takes a single argument of type *Vertex, representing the current // element being iterated over. It returns an error value, which is // returned by the Iterate method. If the callback function returns // an error, iteration is stopped and the error is returned. Iterate(func(v *gograph.Vertex[T]) error) error // Reset resets the iterator to its initial state, allowing the // sequence to be iterated over again from the beginning. Reset() }
Iterator represents a general purpose iterator for iterating over a sequence of graph's vertices. It provides methods for checking if there are more elements to be iterated over, getting the next element, iterating over all elements using a callback function, and resetting the iterator to its initial state.
func NewBreadthFirstIterator ¶
func NewBreadthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error)
NewBreadthFirstIterator creates a new instance of breadthFirstIterator and returns it as the Iterator interface.
func NewClosestFirstIterator ¶ added in v0.2.0
func NewClosestFirstIterator[T comparable](graph gograph.Graph[T], start T) (Iterator[T], error)
NewClosestFirstIterator creates a new instance of closestFirstIterator and returns it as the Iterator interface.
Edge weights are expected to be non-negative. An edge with a NaN weight is skipped. A vertex reached only through a +Inf weight is visited after every vertex at a finite distance.
if the start node doesn't exist, returns error.
func NewDepthFirstIterator ¶
func NewDepthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error)
NewDepthFirstIterator creates a new instance of depthFirstIterator and returns it as the Iterator interface.
func NewRandomWalkIterator ¶ added in v0.2.0
func NewRandomWalkIterator[T comparable](graph gograph.Graph[T], start T, steps int) (Iterator[T], error)
NewRandomWalkIterator creates a new instance of randomWalkIterator and returns it as the Iterator interface.
The walk returns the start vertex first. The steps argument is the maximum number of vertices returned, including the start vertex, so a walk with five steps moves at most four times.
Without gograph.Weighted(), each neighbor has an equal chance of being picked. With gograph.Weighted(), each step picks a neighbor with a probability proportional to the weight of the edge to it. Edges with a weight of zero or less are never picked, unless none of the edges of the current vertex has a positive weight. Then each of them has an equal chance. An edge with a weight of positive infinity is picked over any edge with a finite weight.
func NewTopologicalIterator ¶
func NewTopologicalIterator[T comparable](g gograph.Graph[T]) (Iterator[T], error)
NewTopologicalIterator creates a new instance of topologicalIterator and returns it as the Iterator interface. It returns gograph.ErrNotDirected if the graph is not directed, and gograph.ErrDAGHasCycle if the graph has a cycle.
