ginger/gg/gg.go

489 lines
13 KiB
Go
Raw Normal View History

// Package gg implements ginger graph creation, traversal, and (de)serialization
package gg
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
)
// 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{}
}
// 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,
}
2017-11-24 18:05:58 +00:00
}
// VertexType enumerates the different possible vertex types
type VertexType string
const (
// ValueVertex is a Vertex which contains exactly one value and has at least
// one edge (either input or output)
ValueVertex VertexType = "value"
// JunctionVertex is a Vertex which contains two or more in edges and
// exactly one out edge
JunctionVertex VertexType = "junction"
)
// Edge is a uni-directional connection between two vertices with an attribute
// value
type Edge struct {
From *Vertex
Value Value
To *Vertex
}
// Vertex is a vertex in a Graph. No fields should be modified directly, only
// through method calls
type Vertex struct {
ID string
VertexType
Value Value // Value is valid if-and-only-if VertexType is ValueVertex
In, Out []Edge
}
////////////////////////////////////////////////////////////////////////////////
2017-11-05 16:11:05 +00:00
// OpenEdge is an un-realized Edge which can't be used for anything except
// constructing graphs. It has no meaning on its own.
2017-11-05 16:11:05 +00:00
type OpenEdge struct {
// fromV will be the source vertex as-if the vertex (and any sub-vertices of
// it) doesn't already exist in the graph. If it or it's sub-vertices does
// already that will need to be taken into account when persisting into the
// graph
fromV vertex
val Value
}
func (oe OpenEdge) id() string {
return fmt.Sprintf("(%s,%s)", oe.fromV.id, oe.val.ID)
}
// vertex is a representation of a vertex in the graph. Each Graph contains a
// set of all the Value vertex instances it knows about. Each of these contains
2017-11-05 16:11:05 +00:00
// all the input OpenEdges which are known for it. So you can think of these
// "top-level" Value vertex instances as root nodes in a tree, and each OpenEdge
// as a branch.
//
2017-11-05 16:11:05 +00:00
// If a OpenEdge contains a fromV which is a Value that vertex won't have its in
// slice populated no matter what. If fromV is a Junction it will be populated,
// with any sub-Value's not being populated and so-on recursively
//
// When a view is constructed in makeView these Value instances are deduplicated
// and the top-level one's in value is used to properly connect it.
type vertex struct {
id string
VertexType
val Value
2017-11-05 16:11:05 +00:00
in []OpenEdge
}
func (v vertex) cp() vertex {
cp := v
2017-11-05 16:11:05 +00:00
cp.in = make([]OpenEdge, len(v.in))
copy(cp.in, v.in)
return cp
}
2017-11-05 16:11:05 +00:00
func (v vertex) hasOpenEdge(oe OpenEdge) bool {
oeID := oe.id()
for _, in := range v.in {
if in.id() == oeID {
return true
}
}
return false
}
2017-11-05 16:11:05 +00:00
func (v vertex) cpAndDelOpenEdge(oe OpenEdge) (vertex, bool) {
oeID := oe.id()
for i, in := range v.in {
if in.id() == oeID {
v = v.cp()
v.in = append(v.in[:i], v.in[i+1:]...)
return v, true
}
}
return v, false
}
// Graph is a wrapper around a set of connected Vertices
type Graph struct {
vM map[string]vertex // only contains value vertices
// generated by makeView on-demand
byVal map[string]*Vertex
all map[string]*Vertex
}
// Null is the root empty graph, and is the base off which all graphs are built
var Null = &Graph{
vM: map[string]vertex{},
byVal: map[string]*Vertex{},
all: map[string]*Vertex{},
}
// this does _not_ copy the view, as it's assumed the only reason to copy a
// graph is to modify it anyway
func (g *Graph) cp() *Graph {
cp := &Graph{
vM: make(map[string]vertex, len(g.vM)),
}
for vID, v := range g.vM {
cp.vM[vID] = v
}
return cp
}
////////////////////////////////////////////////////////////////////////////////
// Graph creation
func mkVertex(typ VertexType, val Value, ins []OpenEdge) vertex {
v := vertex{VertexType: typ, in: ins}
switch typ {
case ValueVertex:
v.id = val.ID
v.val = val
case JunctionVertex:
inIDs := make([]string, len(ins))
for i := range ins {
inIDs[i] = ins[i].id()
}
v.id = "[" + strings.Join(inIDs, ",") + "]"
default:
panic(fmt.Sprintf("unknown vertex type %q", typ))
}
return v
}
2017-11-05 16:11:05 +00:00
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents
// an edge (with edgeVal attached to it) coming from the ValueVertex containing
// val.
//
// When constructing Graphs, Value vertices are de-duplicated on their Value. So
2017-11-05 16:11:05 +00:00
// multiple ValueOut OpenEdges constructed with the same val will be leaving the
// same Vertex instance in the constructed Graph.
func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{fromV: mkVertex(ValueVertex, val, nil), val: edgeVal}
}
2017-11-05 16:11:05 +00:00
// JunctionOut creates a OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) coming from the
// JunctionVertex comprised of the given ordered-set of input edges.
//
// When constructing Graphs Junction vertices are de-duplicated on their input
2017-11-05 16:11:05 +00:00
// edges. So multiple Junction OpenEdges constructed with the same set of input
// edges will be leaving the same Junction instance in the constructed Graph.
func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
2017-11-05 16:11:05 +00:00
return OpenEdge{
fromV: mkVertex(JunctionVertex, Value{}, in),
val: edgeVal,
}
}
2017-11-05 16:11:05 +00:00
// AddValueIn takes a OpenEdge and connects it to the Value Vertex containing
// val, returning the new Graph which reflects that connection. Any Vertices
2017-11-05 16:11:05 +00:00
// referenced within toe OpenEdge which do not yet exist in the Graph will also
// be created in this step.
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil)
toID := to.id
// if to is already in the graph, pull it out, as it might have existing in
// edges we want to keep
if exTo, ok := g.vM[toID]; ok {
to = exTo
}
// if the incoming edge already exists in to then there's nothing to do
2017-11-05 16:11:05 +00:00
if to.hasOpenEdge(oe) {
return g
}
to = to.cp()
2017-11-05 16:11:05 +00:00
to.in = append(to.in, oe)
g = g.cp()
// starting with to (which we always overwrite) go through vM and
// recursively add in any vertices which aren't already there
var persist func(vertex)
persist = func(v vertex) {
if v.VertexType == ValueVertex {
vID := v.id
if _, ok := g.vM[vID]; !ok {
g.vM[vID] = v
}
} else {
for _, e := range v.in {
persist(e.fromV)
}
}
}
delete(g.vM, toID)
persist(to)
for _, e := range to.in {
persist(e.fromV)
}
return g
}
2017-11-05 16:11:05 +00:00
// DelValueIn takes a OpenEdge and disconnects it from the Value Vertex
// containing val, returning the new Graph which reflects the disconnection. If
// the Value Vertex doesn't exist within the graph, or it doesn't have the given
2017-11-05 16:11:05 +00:00
// OpenEdge, no changes are made. Any vertices referenced by toe OpenEdge for
// which that edge is their only outgoing edge will be removed from the Graph.
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil)
toID := to.id
// pull to out of the graph. if it's not there then bail
var ok bool
if to, ok = g.vM[toID]; !ok {
return g
}
// get new copy of to without the half-edge, or return if the half-edge
// wasn't even in to
2017-11-05 16:11:05 +00:00
to, ok = to.cpAndDelOpenEdge(oe)
if !ok {
return g
}
g = g.cp()
g.vM[toID] = to
// connectedTo returns whether the vertex has any connections with the
// vertex of the given id, descending recursively
var connectedTo func(string, vertex) bool
connectedTo = func(vID string, curr vertex) bool {
for _, in := range curr.in {
if in.fromV.VertexType == ValueVertex && in.fromV.id == vID {
return true
} else if in.fromV.VertexType == JunctionVertex && connectedTo(vID, in.fromV) {
return true
}
}
return false
}
// isOrphaned returns whether the given vertex has any connections to other
// nodes in the graph
isOrphaned := func(v vertex) bool {
vID := v.id
if v, ok := g.vM[vID]; ok && len(v.in) > 0 {
return false
}
for vID2, v2 := range g.vM {
if vID2 == vID {
continue
} else if connectedTo(vID, v2) {
return false
}
}
return true
}
// if to is orphaned get rid of it
if isOrphaned(to) {
delete(g.vM, toID)
}
2017-11-05 16:11:05 +00:00
// rmOrphaned descends down the given OpenEdge and removes any Value
// Vertices referenced in it which are now orphaned
2017-11-05 16:11:05 +00:00
var rmOrphaned func(OpenEdge)
rmOrphaned = func(oe OpenEdge) {
if oe.fromV.VertexType == ValueVertex && isOrphaned(oe.fromV) {
delete(g.vM, oe.fromV.id)
} else if oe.fromV.VertexType == JunctionVertex {
for _, juncOe := range oe.fromV.in {
rmOrphaned(juncOe)
}
}
}
2017-11-05 16:11:05 +00:00
rmOrphaned(oe)
return g
}
// Union takes in another Graph and returns a new one which is the union of the
// two. Value vertices which are shared between the two will be merged so that
// the new vertex has the input edges of both.
func (g *Graph) Union(g2 *Graph) *Graph {
g = g.cp()
for vID, v2 := range g2.vM {
v, ok := g.vM[vID]
if !ok {
v = v2
} else {
for _, v2e := range v2.in {
2017-11-05 16:11:05 +00:00
if !v.hasOpenEdge(v2e) {
v.in = append(v.in, v2e)
}
}
}
g.vM[vID] = v
}
return g
}
////////////////////////////////////////////////////////////////////////////////
// Graph traversal
func (g *Graph) makeView() {
if g.byVal != nil {
return
}
// view only contains value vertices, but we need to keep track of all
// vertices while constructing the view
g.byVal = make(map[string]*Vertex, len(g.vM))
g.all = map[string]*Vertex{}
var getV func(vertex, bool) *Vertex
getV = func(v vertex, top bool) *Vertex {
V, ok := g.all[v.id]
if !ok {
V = &Vertex{ID: v.id, VertexType: v.VertexType, Value: v.val}
g.all[v.id] = V
}
// we can be sure all Value vertices will be called with top==true at
// some point, so we only need to descend into the input edges if:
// * top is true
// * this is a junction's first time being gotten
if !top && (ok || v.VertexType != JunctionVertex) {
return V
}
V.In = make([]Edge, 0, len(v.in))
for i := range v.in {
fromV := getV(v.in[i].fromV, false)
e := Edge{From: fromV, Value: v.in[i].val, To: V}
fromV.Out = append(fromV.Out, e)
V.In = append(V.In, e)
}
if v.VertexType == ValueVertex {
g.byVal[v.val.ID] = V
}
return V
}
for _, v := range g.vM {
getV(v, true)
}
}
// ValueVertex returns the Value Vertex for the given value. If the Graph
// doesn't contain a vertex for the value then nil is returned
func (g *Graph) ValueVertex(val Value) *Vertex {
g.makeView()
return g.byVal[val.ID]
}
// ValueVertices returns all Value Vertices in the Graph
func (g *Graph) ValueVertices() []*Vertex {
g.makeView()
vv := make([]*Vertex, 0, len(g.byVal))
for _, v := range g.byVal {
vv = append(vv, v)
}
return vv
}
// Equal returns whether or not the two Graphs are equivalent in value
func Equal(g1, g2 *Graph) bool {
if len(g1.vM) != len(g2.vM) {
return false
}
for v1ID, v1 := range g1.vM {
v2, ok := g2.vM[v1ID]
if !ok {
return false
}
// since the vertices are values we must make sure their input sets are
// the same (which is tricky since they're unordered, unlike a
// junction's)
if len(v1.in) != len(v2.in) {
return false
}
for _, in := range v1.in {
2017-11-05 16:11:05 +00:00
if !v2.hasOpenEdge(in) {
return false
}
}
}
return true
}
2017-11-05 16:57:57 +00:00
// TODO Walk, but by edge
// TODO Walk, but without end. AKA FSM
2017-11-05 16:57:57 +00:00
// Walk will traverse the Graph, calling the callback on every Vertex in the
// Graph once. If startWith is non-nil then that Vertex will be the first one
// passed to the callback and used as the starting point of the traversal. If
// the callback returns false the traversal is stopped.
func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
// TODO figure out how to make Walk deterministic
2017-11-05 16:57:57 +00:00
g.makeView()
if len(g.byVal) == 0 {
2017-11-05 16:57:57 +00:00
return
}
seen := make(map[*Vertex]bool, len(g.byVal))
2017-11-05 16:57:57 +00:00
var innerWalk func(*Vertex) bool
innerWalk = func(v *Vertex) bool {
if seen[v] {
return true
} else if !callback(v) {
return false
}
seen[v] = true
for _, e := range v.In {
if !innerWalk(e.From) {
return false
}
}
return true
}
if startWith != nil {
if !innerWalk(startWith) {
return
}
}
for _, v := range g.byVal {
2017-11-05 16:57:57 +00:00
if !innerWalk(v) {
return
}
}
}
// ByID returns all vertices indexed by their ID field
func (g *Graph) ByID() map[string]*Vertex {
g.makeView()
return g.all
}