Documentation
¶
Overview ¶
Example ¶
package main
import (
"errors"
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
// An edge A -> B means A has to happen before B.
g := gograph.New[string](gograph.Acyclic())
checkout := g.AddVertexByLabel("checkout")
build := g.AddVertexByLabel("build")
test := g.AddVertexByLabel("test")
release := g.AddVertexByLabel("release")
_, _ = g.AddEdge(checkout, build)
_, _ = g.AddEdge(build, test)
_, _ = g.AddEdge(test, release)
// Acyclic graphs reject any edge that would create a cycle.
_, err := g.AddEdge(release, checkout)
fmt.Println(errors.Is(err, gograph.ErrDAGCycle))
order, _ := gograph.TopologySort(g)
for _, v := range order {
fmt.Println(v.Label())
}
}
Output: true checkout build test release
Example (BreadthFirst) ¶
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"))
it, err := traverse.NewBreadthFirstIterator(g, "A")
if err != nil {
fmt.Println(err)
return
}
for it.HasNext() {
fmt.Println(it.Next().Label())
}
}
Output: A B C D
Example (UndirectedEdgeCounts) ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string]()
a := g.AddVertexByLabel("A")
b := g.AddVertexByLabel("B")
c := g.AddVertexByLabel("C")
_, _ = g.AddEdge(a, b)
_, _ = g.AddEdge(a, c)
fmt.Println(g.Size(), len(g.AllEdges()), len(g.EdgesOf(a)), len(g.GetAllEdges(a, b)))
fmt.Println(a.Degree(), a.InDegree(), a.OutDegree())
}
Output: 4 4 4 2 4 2 2
Index ¶
- Variables
- func ConnectedComponents[T comparable](g Graph[T]) [][]*Vertex[T]
- func Edges[T comparable](g Graph[T]) iter.Seq[*Edge[T]]
- func HasCycle[T comparable](g Graph[T]) bool
- func Vertices[T comparable](g Graph[T]) iter.Seq[*Vertex[T]]
- type DiffOption
- type Edge
- type EdgeChange
- type EdgeEntry
- type EdgeOptionFunc
- type EdgeProperties
- type Graph
- type GraphDiff
- type GraphOptionFunc
- type GraphProperties
- type GraphType
- type PropertiesSummary
- type Vertex
- func (v *Vertex[T]) AllNeighbors() iter.Seq[*Vertex[T]]
- func (v *Vertex[T]) Degree() int
- func (v *Vertex[T]) HasNeighbor(vertex *Vertex[T]) bool
- func (v *Vertex[T]) InDegree() int
- func (v *Vertex[T]) Label() T
- func (v *Vertex[T]) Metadata() any
- func (v *Vertex[T]) NeighborByLabel(label T) *Vertex[T]
- func (v *Vertex[T]) Neighbors() []*Vertex[T]
- func (v *Vertex[T]) OutDegree() int
- func (v *Vertex[T]) Weight() float64
- type VertexChange
- type VertexEntry
- type VertexOptionFunc
- type VertexProperties
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrIncompatibleGraphs is returned by Diff when one graph is directed and // the other is undirected, since their edges can't be matched. ErrIncompatibleGraphs = errors.New("graphs are not both directed or both undirected") // ErrDiffMismatch is returned by GraphDiff.Apply when the diff doesn't fit // the graph it's applied to. ErrDiffMismatch = errors.New("diff does not match the graph") )
var ( ErrNilVertices = errors.New("vertices are nil") ErrVertexDoesNotExist = errors.New("vertex does not exist") ErrEdgeAlreadyExists = errors.New("edge already exists") ErrDAGCycle = errors.New("edges would create cycle") ErrDAGHasCycle = errors.New("the graph contains a cycle") ErrNotDirected = errors.New("graph is not directed") )
Functions ¶
func ConnectedComponents ¶ added in v0.9.0
func ConnectedComponents[T comparable](g Graph[T]) [][]*Vertex[T]
ConnectedComponents returns the connected components of g. For a directed graph, edge directions are ignored, so it returns the weakly connected components. The vertices in the result are the vertices stored in g, not copies, and an empty graph returns an empty slice.
The components are in the order of their first vertex in GetAllVertices. In an undirected graph, the vertices of a component are in breadth-first order from that vertex, following the order the edges were added. In a directed graph, they are in GetAllVertices order. The components share one backing array, but appending to one doesn't change the others.
A graph from New is read from the neighbor lists stored on its vertices. An undirected one takes O(V+E) time. A directed one takes O((V+E)·α(V)) time, where α is the inverse Ackermann function, which is below 5 for any graph that fits in memory. Both use O(V) extra space. Other directed Graph implementations are read through GetAllVertices and AllEdges, also in O((V+E)·α(V)) time. Other undirected Graph implementations are read through GetAllVertices and EdgesOf, with one EdgesOf call per vertex. It panics if g is nil.
connectivity.ConnectedComponents calls this.
Example ¶
g := New[string]()
_, _ = g.AddEdge(NewVertex("a"), NewVertex("b"))
_, _ = g.AddEdge(NewVertex("b"), NewVertex("c"))
_, _ = g.AddEdge(NewVertex("d"), NewVertex("e"))
g.AddVertexByLabel("f")
for _, component := range ConnectedComponents(g) {
var labels []string
for _, v := range component {
labels = append(labels, v.Label())
}
fmt.Println(labels)
}
Output: [a b c] [d e] [f]
func Edges ¶ added in v0.10.0
func Edges[T comparable](g Graph[T]) iter.Seq[*Edge[T]]
Edges returns the edges of g in the order of AllEdges. In an undirected graph, an edge between two vertices comes once in each direction, and a self-loop once. If g changes during the loop, edges can be skipped or repeated.
For a graph from New, it reads the edges in place: ranging over all of them takes O(V+E) time and allocates nothing. For another implementation of Graph, it calls AllEdges before the first edge.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string](gograph.Directed(), gograph.Weighted())
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("B"), gograph.WithEdgeWeight(2))
_, _ = g.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("C"), gograph.WithEdgeWeight(5))
_, _ = g.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("C"), gograph.WithEdgeWeight(1))
total := 0.0
for e := range gograph.Edges(g) {
total += e.Weight()
}
fmt.Println(total)
}
Output: 8
func HasCycle ¶ added in v0.9.0
func HasCycle[T comparable](g Graph[T]) bool
HasCycle reports whether g contains a cycle. A self-loop is a cycle. In a directed graph, a cycle follows the edge directions. In an undirected graph, the two stored directions of an edge count as one edge, so any other cycle needs at least three vertices.
A graph created with Acyclic refuses edges that would create a cycle, so it returns false without a search. A graph from New takes O(V+E) time and O(V) extra space, and the search stops at the first cycle it finds. Any other graph is read through its methods: TopologySort when it is directed, and AllNeighbors when it is undirected. It panics if g is nil.
connectivity.HasCycle calls this.
Example ¶
g := New[string](Directed())
_, _ = g.AddEdge(NewVertex("build"), NewVertex("test"))
_, _ = g.AddEdge(NewVertex("test"), NewVertex("deploy"))
fmt.Println(HasCycle(g))
_, _ = g.AddEdge(g.GetVertexByID("deploy"), g.GetVertexByID("build"))
fmt.Println(HasCycle(g))
Output: false true
func Vertices ¶ added in v0.10.0
func Vertices[T comparable](g Graph[T]) iter.Seq[*Vertex[T]]
Vertices returns the vertices of g in the order of GetAllVertices. Each vertex is the graph's own vertex. If g changes during the loop, vertices can be skipped or repeated.
For a graph from New, it reads the vertices in place: ranging over all of them takes O(V) time and allocates nothing. For another implementation of Graph, it calls GetAllVertices before the first vertex.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
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("C"), gograph.NewVertex("B"))
// the vertices with no outgoing edges
for v := range gograph.Vertices(g) {
if v.OutDegree() == 0 {
fmt.Println(v.Label())
}
}
}
Output: B
Types ¶
type DiffOption ¶ added in v0.10.0
type DiffOption func(*diffOptions)
DiffOption changes how Diff compares two graphs.
func WithWeightTolerance ¶ added in v0.10.0
func WithWeightTolerance(eps float64) DiffOption
WithWeightTolerance makes Diff treat two weights as equal when they differ by at most eps. A negative or NaN eps keeps the exact comparison.
type Edge ¶
type Edge[T comparable] struct { // contains filtered or unexported fields }
Edge represents an edges in a graph. It contains start and end points.
func NewEdge ¶
func NewEdge[T comparable](source *Vertex[T], dest *Vertex[T], options ...EdgeOptionFunc) *Edge[T]
func (*Edge[T]) Destination ¶ added in v0.2.0
Destination returns edge dest vertex
func (*Edge[T]) InsertionIndex ¶ added in v0.9.0
InsertionIndex reports the order in which this edge was added to its graph. Edges are numbered from 1 in the order they are stored. On an undirected graph each AddEdge stores the opposite direction first, so the edge AddEdge returns is not always number 1. An edge created with NewEdge and not added to a graph returns 0.
func (*Edge[T]) Label ¶ added in v0.9.0
Label returns the edge label. It returns an empty string when no label was set.
func (*Edge[T]) OtherVertex ¶ added in v0.2.0
OtherVertex accepts the label of one the vertices of the edge and returns the other one. If the input label doesn't match to either of the vertices, returns nil.
type EdgeChange ¶ added in v0.10.0
type EdgeChange[T comparable] struct { From, To T OldWeight, NewWeight float64 OldLabel, NewLabel string }
EdgeChange is an edge whose weight or edge label differs between the two graphs.
type EdgeEntry ¶ added in v0.10.0
type EdgeEntry[T comparable] struct { From, To T Weight float64 Label string }
EdgeEntry is an edge that a GraphDiff adds or removes, with its weight and its edge label.
type EdgeOptionFunc ¶
type EdgeOptionFunc func(properties *EdgeProperties)
EdgeOptionFunc represent an alias of function type that modifies the specified edge properties.
func WithEdgeLabel ¶ added in v0.9.0
func WithEdgeLabel(label string) EdgeOptionFunc
WithEdgeLabel sets a label on the edge. An empty label means the edge has no label.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string](gograph.Directed())
edge, _ := g.AddEdge(
gograph.NewVertex("service"),
gograph.NewVertex("database"),
gograph.WithEdgeLabel("queries"),
)
fmt.Println(edge.Label())
}
Output: queries
func WithEdgeWeight ¶
func WithEdgeWeight(weight float64) EdgeOptionFunc
WithEdgeWeight sets the edge weight for the specified edge properties in the returned EdgeOptionFunc.
type EdgeProperties ¶
type EdgeProperties struct {
// contains filtered or unexported fields
}
EdgeProperties represents the properties of an edge.
type Graph ¶
type Graph[T comparable] interface { GraphType // AddEdge adds an edge from the vertex with the 'from' label to // the vertex with the 'to' label by appending the 'to' vertex to the // 'neighbors' slice of the 'from' vertex, in directed graph. // // In undirected graph, it also adds an edge from the vertex with // the 'to' label to the vertex with the 'from' label by appending // the 'from' vertex to the 'neighbors' slice of the 'to' vertex. it // means that it create the edges in both direction between the specified // vertices. A self-loop is stored once. // // This method accepts additional edge options such as weight and adds // them to the new edge. // // In a graph created with Acyclic, an edge that would close a cycle // returns ErrDAGCycle without adding the edge. A graph from New keeps a // topological order of its vertices for this check. An edge that agrees // with the order, an edge from a vertex with no incoming edges, and an // edge to a vertex with no outgoing edges take O(1) time. Any other edge // searches forward from the 'to' vertex and backward from the 'from' // vertex, and moves the vertices it finds. That takes at most O(R log R) // time for the R vertices and edges reachable from the 'to' vertex, and // O((V+E) log V) in the worst case. On a graph that Clone copied from // another implementation of Graph, the first AddEdge also builds the // order in O(V+E) time. // // It creates the input vertices if they don't exist in the graph, the // same way AddVertex does, so a vertex from another graph is copied. // If any of the specified vertices is nil, returns nil. // If edge already exist, returns error. AddEdge(from, to *Vertex[T], options ...EdgeOptionFunc) (*Edge[T], error) // GetAllEdges returns a slice of all edges connecting source vertex to // target vertex if such vertices exist in this graph. // // In directed graph, or if both vertices are the same, it returns a single edge. // // If any of the specified vertices is nil, returns nil. // If any of the vertices does not exist, returns nil. // If both vertices exist but no edges found, returns nil. GetAllEdges(from, to *Vertex[T]) []*Edge[T] // AllEdges returns all the edges in the graph. The edges are grouped by // source vertex in the order of GetAllVertices, and the edges of each // source vertex are in the order they were added. In an undirected // graph, an edge between distinct vertices is stored in both directions, // so it appears in the group of both its vertices. A self-loop is stored once. AllEdges() []*Edge[T] // GetEdge returns an edge connecting source vertex to target vertex // if such vertices and such edge exist in this graph. // // In undirected graph, returns only the edge from the "from" vertex to // the "to" vertex. // // If any of the specified vertices is nil, returns nil. // If edge does not exist, returns nil. GetEdge(from, to *Vertex[T]) *Edge[T] // EdgesOf returns a slice of all edges touching the specified vertex. // If no edges are touching the specified vertex returns nil. // // An undirected edge between distinct vertices appears in both directions. // Use v.OutDegree() for the number of neighbors in an undirected graph. // // The edges that start from the vertex come first, in the order they // were added. The edges that end at the vertex follow, in the order of // their source vertices in GetAllVertices. For a graph from New, it takes // O(d log d) time for a vertex with d edges. // // If the input vertex is nil, returns nil. // If the input vertex does not exist, returns nil. EdgesOf(v *Vertex[T]) []*Edge[T] // RemoveEdges removes input edges from the graph from the specified // slice of edges, if they exist. In undirected graph, removes edges // in both directions. For a graph from New, removing an edge takes time // proportional to the degrees of its two vertices. RemoveEdges(edges ...*Edge[T]) // AddVertexByLabel adds a new vertex with the given label to the graph. // Label of the vertex is a comparable type. This method also accepts the // vertex properties such as weight. // // If there is a vertex with the same label in the graph, returns nil. // Otherwise, returns the created vertex. AddVertexByLabel(label T, options ...VertexOptionFunc) *Vertex[T] // AddVertex adds the input vertex to the graph. It doesn't add // vertex to the graph if the input vertex label is already exists // in the graph. // // A vertex belongs to one graph. If the input vertex has already been // added to a graph, including this one before it was removed, the graph // stores a copy with the same label, weight and metadata, and no edges. AddVertex(v *Vertex[T]) // GetVertexByID returns the vertex with the input label. // // If vertex doesn't exist, returns nil. GetVertexByID(label T) *Vertex[T] // GetAllVerticesByID returns a slice of vertices with the specified label list. // // If vertex doesn't exist, doesn't add nil to the output list. GetAllVerticesByID(label ...T) []*Vertex[T] // GetAllVertices returns a slice of all existing vertices in the graph, // in the order they were added. A vertex that is removed and added // again moves to the end. GetAllVertices() []*Vertex[T] // RemoveVertices removes all the specified vertices from this graph including // all its touching edges if present. A removed vertex reports no neighbors // and an in-degree of 0. A vertex of another graph with the same label as a // removed one keeps its edges. For a graph from New, removing a vertex // takes time proportional to the degrees of its neighbors. RemoveVertices(vertices ...*Vertex[T]) // ContainsEdge returns 'true' if and only if this graph contains an edge // going from the source vertex to the target vertex. // // If any of the specified vertices does not exist in the graph, or if is nil, // returns 'false'. ContainsEdge(from, to *Vertex[T]) bool // ContainsVertex returns 'true' if this graph contains the specified vertex. // // If the specified vertex is nil, returns 'false'. ContainsVertex(v *Vertex[T]) bool // Order returns the number of vertices in the graph. Order() uint32 // Size returns the number of stored edges in the graph. An undirected edge // between distinct vertices is stored in both directions and counts twice. Size() uint32 }
Graph defines methods for managing a graph with vertices and edges. It is the base interface in the graph hierarchy. Each graph object contains a set of vertices and edges.
Through generics, a graph can be typed to specific classes for vertices' label T.
func Clone ¶ added in v0.9.1
func Clone[T comparable](g Graph[T]) Graph[T]
Clone returns an independent copy of g, preserving its directed, weighted, and acyclic properties, vertex and edge weights, labels, edge insertion indexes, and iteration order. Vertices and edges are newly allocated. Metadata is copied as an interface value; reference values stored in metadata still refer to the same data. A nil graph panics. The graph must not be modified while it is being copied.
The copy keeps its vertices, its edges, its neighbor lists and the lists of edges that end at each vertex in one array each, so a vertex or edge of the copy that is still referenced keeps that storage alive.
Time and space are O(V + E).
Example ¶
g := New[string](Directed(), Weighted())
a := g.AddVertexByLabel("a")
b := g.AddVertexByLabel("b")
_, err := g.AddEdge(a, b, WithEdgeWeight(5))
if err != nil {
panic(err)
}
copy := Clone(g)
copy.RemoveVertices(copy.GetVertexByID("a"))
fmt.Println(g.Order(), copy.Order(), copy.IsDirected(), copy.IsWeighted())
Output: 2 1 true true
func InducedSubgraph ¶ added in v0.9.0
func InducedSubgraph[T comparable](g Graph[T], labels ...T) (Graph[T], error)
InducedSubgraph returns a new graph with the given vertices and every edge of g whose endpoints are both in that set.
The result has the same directed, weighted, and acyclic properties as g. Vertex weights, edge weights, edge labels, metadata, and edge insertion indexes are copied. The result does not share vertices or edges with g. Duplicate labels are ignored, and vertex order follows the first occurrence of each label. No labels returns an empty graph with the same properties.
A label that is not in g returns ErrVertexDoesNotExist wrapped with that label, and the returned graph is nil. A nil graph, or a graph that was not created by New, panics.
Time is O(k + D), where k is the number of distinct labels and D is the sum of the out-degrees of those vertices. Extra space beyond the result is O(k).
Example ¶
g := New[string](Directed(), Weighted())
a := g.AddVertexByLabel("api")
b := g.AddVertexByLabel("auth")
c := g.AddVertexByLabel("billing")
_, _ = g.AddEdge(a, b, WithEdgeWeight(1))
_, _ = g.AddEdge(b, c, WithEdgeWeight(2))
_, _ = g.AddEdge(a, c, WithEdgeWeight(4))
sub, err := InducedSubgraph(g, "api", "billing")
if err != nil {
fmt.Println(err)
return
}
for _, v := range sub.GetAllVertices() {
fmt.Println(v.Label())
}
edge := sub.GetEdge(sub.GetVertexByID("api"), sub.GetVertexByID("billing"))
fmt.Println(edge.Weight())
Output: api billing 4
func New ¶
func New[T comparable](options ...GraphOptionFunc) Graph[T]
New creates a new instance of base graph that implemented the Graph interface.
Example (Acyclic) ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[int](gograph.Acyclic())
_, _ = g.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2))
_, _ = g.AddEdge(gograph.NewVertex(2), gograph.NewVertex(3))
_, err := g.AddEdge(gograph.NewVertex(3), gograph.NewVertex(1))
fmt.Println(err)
}
Output: edges would create cycle
Example (Directed) ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[int](gograph.Directed())
_, _ = g.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2))
_, _ = g.AddEdge(gograph.NewVertex(1), gograph.NewVertex(3))
_, _ = g.AddEdge(gograph.NewVertex(2), gograph.NewVertex(4))
_, _ = g.AddEdge(gograph.NewVertex(3), gograph.NewVertex(4))
_, _ = g.AddEdge(gograph.NewVertex(4), gograph.NewVertex(5))
_, _ = g.AddEdge(gograph.NewVertex(5), gograph.NewVertex(6))
fmt.Println(g.Order(), g.Size())
for _, e := range g.AllEdges() {
fmt.Println(e.Source().Label(), "->", e.Destination().Label())
}
}
Output: 6 6 1 -> 2 1 -> 3 2 -> 4 3 -> 4 4 -> 5 5 -> 6
Example (Undirected) ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
// Graphs are undirected by default.
g := gograph.New[string]()
a := g.AddVertexByLabel("A")
b := g.AddVertexByLabel("B")
c := g.AddVertexByLabel("C")
d := g.AddVertexByLabel("D")
_, _ = g.AddEdge(a, b)
_, _ = g.AddEdge(a, d)
_, _ = g.AddEdge(b, c)
_, _ = g.AddEdge(b, d)
// Every undirected edge can be followed both ways.
fmt.Println(g.ContainsEdge(a, b), g.ContainsEdge(b, a))
}
Output: true true
Example (Weighted) ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/path"
)
func main() {
g := gograph.New[string](gograph.Weighted())
a := g.AddVertexByLabel("A")
b := g.AddVertexByLabel("B")
c := g.AddVertexByLabel("C")
d := g.AddVertexByLabel("D")
_, _ = g.AddEdge(a, b, gograph.WithEdgeWeight(4))
_, _ = g.AddEdge(a, d, gograph.WithEdgeWeight(3))
_, _ = g.AddEdge(b, c, gograph.WithEdgeWeight(3))
_, _ = g.AddEdge(b, d, gograph.WithEdgeWeight(1))
_, _ = g.AddEdge(c, d, gograph.WithEdgeWeight(2))
dist := path.Dijkstra(g, "A")
fmt.Println(dist["C"])
}
Output: 5
type GraphDiff ¶ added in v0.10.0
type GraphDiff[T comparable] struct { AddedVertices []VertexEntry[T] `json:",omitempty"` RemovedVertices []VertexEntry[T] `json:",omitempty"` ChangedVertices []VertexChange[T] `json:",omitempty"` AddedEdges []EdgeEntry[T] `json:",omitempty"` RemovedEdges []EdgeEntry[T] `json:",omitempty"` ChangedEdges []EdgeChange[T] `json:",omitempty"` // OldProperties and NewProperties are set when the graphs differ in // whether they're weighted or acyclic. OldProperties, NewProperties *PropertiesSummary `json:",omitempty"` }
GraphDiff holds the differences that turn one graph into another, as Diff returns them. Its fields are plain exported values, so encoding/json can write and read it, as long as no weight is NaN or infinite.
func Diff ¶ added in v0.10.0
func Diff[T comparable](a, b Graph[T], opts ...DiffOption) (*GraphDiff[T], error)
Diff returns the differences that turn graph a into graph b. It matches vertices by label and edges by the labels of their two vertices. It compares the weights of vertices and edges, the edge labels, and whether the graphs are weighted or acyclic. It leaves out metadata, the order of the vertices and edges, and the edge insertion indexes.
Weights are equal when they're == or both NaN, so a graph has no differences from itself. WithWeightTolerance also accepts weights that are close. A removed vertex removes its edges, and RemovedEdges lists those edges too, so the diff is complete on its own.
In an undirected graph, each edge is listed once, in the orientation in which it's found first: in a for a removed edge, and in b for an added or changed one. A - B in a and B - A in b are the same edge. If one graph is directed and the other is undirected, it returns ErrIncompatibleGraphs.
Removed vertices and edges come in the order of a, and added and changed ones in the order of b, so the same graphs always give the same diff.
For graphs from New, it takes O(V+E) time for the vertices and edges of both graphs, and allocates only the diff it returns. Any other Graph is read through GetAllVertices, AllEdges, GetVertexByID and GetEdge, so it also takes the O(V+E) space of the slices they return, and an undirected one a map of O(V) more. It panics if a or b is nil.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
before := gograph.New[string](gograph.Directed())
app := before.AddVertexByLabel("app")
_, _ = before.AddEdge(app, gograph.NewVertex("db"), gograph.WithEdgeLabel("reads"))
_, _ = before.AddEdge(app, gograph.NewVertex("log"))
after := gograph.Clone(before)
after.RemoveVertices(after.GetVertexByID("log"))
_, _ = after.AddEdge(after.GetVertexByID("app"), gograph.NewVertex("cache"), gograph.WithEdgeWeight(2))
after.RemoveEdges(after.GetEdge(after.GetVertexByID("app"), after.GetVertexByID("db")))
_, _ = after.AddEdge(after.GetVertexByID("app"), after.GetVertexByID("db"), gograph.WithEdgeLabel("writes"))
d, err := gograph.Diff(before, after)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(d)
}
Output: + vertex cache - vertex log + edge app -> cache weight 2 - edge app -> log ~ edge app -> db label "reads" -> "writes"
func (*GraphDiff[T]) Apply ¶ added in v0.10.0
Apply returns a copy of base with the differences of d made to it, so Diff(a, b) applied to a gives a graph with no differences from b. The copy comes from Clone, so it's a graph from New, and base doesn't change.
It removes the edges, then the vertices, adds the vertices, then the edges, and then sets the changed weights, edge labels and properties. A removed or changed vertex or edge has to be in base, an added one can't be, and an added edge needs both of its vertices. When d changes the properties, base has to have the old ones. The first difference that doesn't fit returns ErrDiffMismatch, wrapped with what it found. New properties that change whether the graph is directed, or that make an undirected graph acyclic, return ErrIncompatibleGraphs. Added edges that close a cycle in an acyclic graph return ErrDAGCycle, and making a graph with a cycle acyclic returns ErrDAGHasCycle. On an error it returns nil. A nil diff returns a copy of base.
Copying base takes O(V+E) time and space. A removed edge or vertex then costs what RemoveEdges or RemoveVertices does, and every other difference O(1). When the result is acyclic and d adds edges or makes it acyclic, one O(V+E) search checks it for a cycle, so a large diff to an acyclic graph costs no more than copying it. It panics if base is nil.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
v1 := gograph.New[string](gograph.Acyclic())
_, _ = v1.AddEdge(gograph.NewVertex("parse"), gograph.NewVertex("check"))
v2 := gograph.Clone(v1)
_, _ = v2.AddEdge(v2.GetVertexByID("check"), gograph.NewVertex("emit"))
d, _ := gograph.Diff(v1, v2)
patched, err := d.Apply(v1)
if err != nil {
fmt.Println(err)
return
}
rest, _ := gograph.Diff(patched, v2)
fmt.Println(v1.Order(), patched.Order(), rest.IsEmpty())
// Applying it again finds the new vertex already there.
_, err = d.Apply(patched)
fmt.Println(errors.Is(err, gograph.ErrDiffMismatch))
}
Output: 2 3 true true
func (*GraphDiff[T]) IsEmpty ¶ added in v0.10.0
IsEmpty reports whether d has no differences. A nil diff is empty.
func (*GraphDiff[T]) String ¶ added in v0.10.0
String returns the differences one per line: "+" for an added vertex or edge, "-" for a removed one and "~" for a change, as in
~ weighted false -> true + vertex D - vertex B weight 2 ~ vertex C weight 1 -> 2 + edge A -> D label "uses" - edge A -> B ~ edge A -> C weight 3 -> 5
A changed property comes first, then the added, removed and changed vertices, then the edges in the same order, each group in the order of the diff. An added or removed item shows its weight when it isn't 0, and an edge its label when it isn't empty. An undirected edge shows the orientation of the diff. An empty diff returns "".
type GraphOptionFunc ¶
type GraphOptionFunc func(properties *GraphProperties)
GraphOptionFunc represent an alias of function type that modifies the specified graph properties.
func Acyclic ¶
func Acyclic() GraphOptionFunc
Acyclic returns a GraphOptionFunc that modifies the specified graph properties. It sets the isAcyclic and isDirected to true. Only direct graph can be acyclic.
func Directed ¶
func Directed() GraphOptionFunc
Directed returns a GraphOptionFunc that modifies the specified graph properties. It sets the isDirected to true.
func Weighted ¶
func Weighted() GraphOptionFunc
Weighted returns a GraphOptionFunc that modifies the specified graph properties. It sets the isWeighted to true.
type GraphProperties ¶
type GraphProperties struct {
// contains filtered or unexported fields
}
GraphProperties represents the properties of a graph.
type GraphType ¶
type GraphType interface {
// IsDirected returns true if the graph is directed, false otherwise.
IsDirected() bool
// IsAcyclic returns true if the graph is acyclic, false otherwise.
IsAcyclic() bool
// IsWeighted returns true if the graph is weighted, false otherwise.
IsWeighted() bool
}
GraphType defines methods to determine the type of graph. A graph can have multiple types. e.g., a directed graph can be a weighted or acyclic.
type PropertiesSummary ¶ added in v0.10.0
type PropertiesSummary struct {
Directed, Weighted, Acyclic bool
}
PropertiesSummary holds the properties a graph reports through GraphType.
type Vertex ¶
type Vertex[T comparable] struct { // contains filtered or unexported fields }
Vertex represents a node or point in a graph
func NewVertex ¶
func NewVertex[T comparable](label T, options ...VertexOptionFunc) *Vertex[T]
NewVertex creates a vertex with the given label and applies the options, such as WithVertexWeight, to it. The vertex doesn't belong to a graph until it's added with AddVertex or AddEdge.
func StableTopologySort ¶ added in v0.8.0
func StableTopologySort[T comparable](g Graph[T], compare func(a, b T) int) ([]*Vertex[T], error)
StableTopologySort performs a topological sort of the graph that picks, at every step, the smallest vertex with no incoming edges left, using compare on the labels. compare returns a negative number if a < b, zero if a == b and a positive number if a > b, so cmp.Compare works for string and number labels.
If compare only returns zero for equal labels, the result doesn't depend on the order the vertices and edges were added. As in TopologySort, an edge from A to B puts A before B.
It returns ErrNotDirected if the graph is not directed, and ErrDAGHasCycle if the graph has a cycle. It runs in O((V + E) log V) time.
Example ¶
package main
import (
"cmp"
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string](gograph.Acyclic())
// "lint" and "build" have no dependencies, so either can go first.
lint := g.AddVertexByLabel("lint")
build := g.AddVertexByLabel("build")
test := g.AddVertexByLabel("test")
_, _ = g.AddEdge(lint, test)
_, _ = g.AddEdge(build, test)
labels := func(vertices []*gograph.Vertex[string]) []string {
out := make([]string, len(vertices))
for i, v := range vertices {
out[i] = v.Label()
}
return out
}
// TopologySort follows the order the vertices were added.
order, _ := gograph.TopologySort(g)
fmt.Println(labels(order))
// With cmp.Compare, the smallest ready label always comes first.
order, _ = gograph.StableTopologySort(g, cmp.Compare[string])
fmt.Println(labels(order))
}
Output: [lint build test] [build lint test]
func TopologySort ¶
func TopologySort[T comparable](g Graph[T]) ([]*Vertex[T], error)
TopologySort performs a topological sort of the graph using Kahn's algorithm. If the sorted list of vertices does not contain all vertices in the graph, it means there is a cycle in the graph.
The order is stable: it only depends on the order of GetAllVertices and the order the edges were added, so a graph built the same way gives the same result on every run. Use StableTopologySort to choose the order of the vertices that the edges don't order.
It returns ErrNotDirected if the graph is not directed, and ErrDAGHasCycle if it finds a cycle in the graph.
func (*Vertex[T]) AllNeighbors ¶ added in v0.10.0
AllNeighbors returns an iterator over the vertices this vertex has an edge to, in the order of Neighbors. For a vertex of a graph, it yields the graph's own vertices without copying them, so each one is the vertex GetVertexByID returns for its label. In an undirected graph it yields each neighbor once, and a vertex with a self-loop yields itself once. A removed vertex yields nothing.
Ranging over all neighbors takes O(OutDegree) time and allocates nothing. If the graph changes during the loop, the iterator can skip or repeat a neighbor.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string](gograph.Directed())
a := g.AddVertexByLabel("A")
for _, label := range []string{"B", "C", "D"} {
_, _ = g.AddEdge(a, g.AddVertexByLabel(label))
}
// The loop gets the graph's own vertices, and can stop early.
for n := range a.AllNeighbors() {
fmt.Println(n.Label(), n == g.GetVertexByID(n.Label()))
if n.Label() == "C" {
break
}
}
}
Output: B true C true
func (*Vertex[T]) Degree ¶ added in v0.2.0
Degree returns the total degree of the vertex which is the sum of in and out degrees. In an undirected graph this counts incoming and outgoing edges separately; use OutDegree for the number of neighbors.
func (*Vertex[T]) HasNeighbor ¶
HasNeighbor checks if the input vertex is the neighbor of the current node or not. It returns 'true' if it finds the input in the neighbors. Otherwise, or if the input is nil, returns 'false'.
func (*Vertex[T]) Metadata ¶ added in v0.7.0
Metadata returns the metadata associated with the vertex.
func (*Vertex[T]) NeighborByLabel ¶
NeighborByLabel iterates over the neighbor slice and returns the vertex which its label is equal to the input label.
It returns nil if there is no neighbor with that label.
func (*Vertex[T]) Neighbors ¶
Neighbors returns a copy of neighbor slice. If the caller changed the result slice, it won't impact the graph or the vertex. The returned vertices are snapshots that share their own neighbor lists with the graph, so their Neighbors, OutDegree and HasNeighbor aren't reliable once the graph changes. Use GetVertexByID to read the current neighbors of a vertex, and AllNeighbors to read them without copies.
type VertexChange ¶ added in v0.10.0
type VertexChange[T comparable] struct { Label T OldWeight, NewWeight float64 }
VertexChange is a vertex whose weight differs between the two graphs.
type VertexEntry ¶ added in v0.10.0
type VertexEntry[T comparable] struct { Label T Weight float64 }
VertexEntry is a vertex that a GraphDiff adds or removes.
type VertexOptionFunc ¶
type VertexOptionFunc func(properties *VertexProperties)
VertexOptionFunc represent an alias of function type that modifies the specified vertex properties.
func WithVertexWeight ¶
func WithVertexWeight(weight float64) VertexOptionFunc
WithVertexWeight sets the weight for the specified vertex properties in the returned VertexOptionFunc.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
)
func main() {
g := gograph.New[string](gograph.Directed(), gograph.Weighted())
a := g.AddVertexByLabel("A", gograph.WithVertexWeight(3))
b := g.AddVertexByLabel("B", gograph.WithVertexWeight(2))
c := g.AddVertexByLabel("C", gograph.WithVertexWeight(4))
_, _ = g.AddEdge(a, b)
_, _ = g.AddEdge(b, c)
fmt.Println(a.Weight(), b.Weight(), c.Weight())
}
Output: 3 2 4
type VertexProperties ¶
type VertexProperties struct {
// contains filtered or unexported fields
}
VertexProperties represents the properties of a vertex.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package dag answers the everyday questions about dependency graphs: what a vertex depends on, what depends on it, what a change affects, and which vertices can run at the same time.
|
Package dag answers the everyday questions about dependency graphs: what a vertex depends on, what depends on it, what a change affects, and which vertices can run at the same time. |
|
encoding
|
|
|
dot
Package dot writes graphs in the DOT language of Graphviz, which dot, the other Graphviz layout tools and many viewers and editor plugins read.
|
Package dot writes graphs in the DOT language of Graphviz, which dot, the other Graphviz layout tools and many viewers and editor plugins read. |
|
mermaid
Package mermaid writes graphs as Mermaid flowcharts, which GitHub, GitLab and many documentation tools render inside Markdown.
|
Package mermaid writes graphs as Mermaid flowcharts, which GitHub, GitLab and many documentation tools render inside Markdown. |
|
examples
|
|
|
gomodgraph
command
Command gomodgraph loads the output of "go mod graph" into a gograph graph and answers a few questions about the module requirements:
|
Command gomodgraph loads the output of "go mod graph" into a gograph graph and answers a few questions about the module requirements: |
|
Package gographtest helps test code that uses gograph, in the style of net/http/httptest.
|
Package gographtest helps test code that uses gograph, in the style of net/http/httptest. |
|
Package traverse visits the vertices of a gograph.Graph in breadth-first, depth-first, closest-first, topological and random walk order.
|
Package traverse visits the vertices of a gograph.Graph in breadth-first, depth-first, closest-first, topological and random walk order. |