ginger/graph/graph.go
2018-08-21 14:46:17 -04:00

268 lines
6.6 KiB
Go

// Package graph implements an immutable unidirectional graph.
package graph
import (
"crypto/rand"
"encoding/hex"
"fmt"
)
// Value wraps a go value in a way such that it will be uniquely identified
// within any Graph and between Graphs. Use NewValue to create a Value instance.
// You can create an instance manually as long as ID is globally unique.
type Value struct {
ID string
V interface{}
}
// Void is the absence of any value.
var Void Value
// NewValue returns a Value instance wrapping any go value. The Value returned
// will be independent of the passed in go value. So if the same go value is
// passed in twice then the two returned Value instances will be treated as
// being different values by Graph.
func NewValue(V interface{}) Value {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return Value{
ID: hex.EncodeToString(b),
V: V,
}
}
// Edge is a directional edge connecting two values in a Graph, the Tail and the
// Head.
type Edge struct {
Tail, Head Value
}
func (e Edge) id() string {
return fmt.Sprintf("%q->%q", e.Tail, e.Head)
}
// an edgeIndex maps valueIDs to a set of edgeIDs. Graph keeps two edgeIndex's,
// one for input edges and one for output edges.
type edgeIndex map[string]map[string]struct{}
func (ei edgeIndex) cp() edgeIndex {
if ei == nil {
return edgeIndex{}
}
ei2 := make(edgeIndex, len(ei))
for valID, edgesM := range ei {
edgesM2 := make(map[string]struct{}, len(edgesM))
for id := range edgesM {
edgesM2[id] = struct{}{}
}
ei2[valID] = edgesM2
}
return ei2
}
func (ei edgeIndex) add(valID, edgeID string) {
edgesM, ok := ei[valID]
if !ok {
edgesM = map[string]struct{}{}
ei[valID] = edgesM
}
edgesM[edgeID] = struct{}{}
}
func (ei edgeIndex) del(valID, edgeID string) {
edgesM, ok := ei[valID]
if !ok {
return
}
delete(edgesM, edgeID)
if len(edgesM) == 0 {
delete(ei, valID)
}
}
// Graph implements an immutable, unidirectional graph which can hold generic
// values. All methods are thread-safe as they don't modify the Graph in any
// way.
//
// The Graph's zero value is the initial empty graph.
//
// The Graph does not keep track of Edge ordering. Assume that all slices of
// Edges are in random order.
type Graph struct {
m map[string]Edge
// these are indices mapping Value IDs to all the in/out edges for that
// Value in the Graph.
vIns, vOuts edgeIndex
}
func (g Graph) cp() Graph {
g2 := Graph{
m: make(map[string]Edge, len(g.m)),
vIns: g.vIns.cp(),
vOuts: g.vOuts.cp(),
}
for id, e := range g.m {
g2.m[id] = e
}
return g2
}
// Add returns a new Graph instance with the given Edge added to it. If the
// original Graph already had that Edge this returns the original Graph.
func (g Graph) Add(e Edge) Graph {
id := e.id()
if _, ok := g.m[id]; ok {
return g
}
g2 := g.cp()
g2.m[id] = e
g2.vIns.add(e.Head.ID, id)
g2.vOuts.add(e.Tail.ID, id)
return g2
}
// Del returns a new Graph instance without the given Edge in it. If the
// original Graph didn't have that Edge this returns the original Graph.
func (g Graph) Del(e Edge) Graph {
id := e.id()
if _, ok := g.m[id]; !ok {
return g
}
g2 := g.cp()
delete(g2.m, id)
g2.vIns.del(e.Head.ID, id)
g2.vOuts.del(e.Tail.ID, id)
return g2
}
// Edges returns all Edges which are part of the Graph
func (g Graph) Edges() []Edge {
edges := make([]Edge, 0, len(g.m))
for _, e := range g.m {
edges = append(edges, e)
}
return edges
}
// NOTE the Node type exists primarily for convenience. As far as Graph's
// internals are concerned it doesn't _really_ exist, and no Graph method should
// ever take Node as a parameter (except the callback functions like in
// Traverse, where it's not really being taken in).
// Node wraps a Value in a Graph to include that Node's input and output Edges
// in that Graph.
type Node struct {
Value
// All Edges in the Graph with this Node's Value as their Head and Tail,
// respectively.
Ins, Outs []Edge
}
// Node returns the Node for the given Value, or false if the Graph doesn't
// contain the Value.
func (g Graph) Node(v Value) (Node, bool) {
n := Node{Value: v}
for edgeID := range g.vIns[v.ID] {
n.Ins = append(n.Ins, g.m[edgeID])
}
for edgeID := range g.vOuts[v.ID] {
n.Outs = append(n.Outs, g.m[edgeID])
}
return n, len(n.Ins) > 0 || len(n.Outs) > 0
}
// Nodes returns a Node for each Value which has at least one Edge in the Graph,
// with the Nodes mapped by their Value's ID.
func (g Graph) Nodes() map[string]Node {
nodesM := make(map[string]Node, len(g.m)*2)
for _, edge := range g.m {
// if head and tail are modified at the same time it messes up the case
// where they are the same node
{
head := nodesM[edge.Head.ID]
head.Value = edge.Head
head.Ins = append(head.Ins, edge)
nodesM[head.ID] = head
}
{
tail := nodesM[edge.Tail.ID]
tail.Value = edge.Tail
tail.Outs = append(tail.Outs, edge)
nodesM[tail.ID] = tail
}
}
return nodesM
}
// Has returns true if the Graph contains at least one Edge with a Head or Tail
// of Value.
func (g Graph) Has(v Value) bool {
if _, ok := g.vIns[v.ID]; ok {
return true
} else if _, ok := g.vOuts[v.ID]; ok {
return true
}
return false
}
// Traverse is used to traverse the Graph until a stopping point is reached.
// Traversal starts with the cursor at the given start Value. Each hop is
// performed by passing the cursor Value's Node into the next function. The
// cursor moves to the returned Value and next is called again, and so on.
//
// If the boolean returned from the next function is false traversal stops and
// this method returns.
//
// If start has no Edges in the Graph, or a Value returned from next doesn't,
// this will still call next, but the Node will be the zero value.
func (g Graph) Traverse(start Value, next func(n Node) (Value, bool)) {
curr := start
for {
currNode, ok := g.Node(curr)
if ok {
curr, ok = next(currNode)
} else {
curr, ok = next(Node{})
}
if !ok {
return
}
}
}
func (g Graph) edgesShared(g2 Graph) bool {
for id := range g2.m {
if _, ok := g.m[id]; !ok {
return false
}
}
return true
}
// SubGraph returns true if the given Graph shares all of its Edges with this
// Graph.
func (g Graph) SubGraph(g2 Graph) bool {
// as a quick check before iterating through the edges, if g has fewer edges
// than g2 then g2 can't possibly be a sub-graph of it
if len(g.m) < len(g2.m) {
return false
}
return g.edgesShared(g2)
}
// Equal returns true if the given Graph and this Graph have exactly the same
// Edges.
func (g Graph) Equal(g2 Graph) bool {
if len(g.m) != len(g2.m) {
return false
}
return g.edgesShared(g2)
}