ginger/gg/gg.go
Brian Picciano 82e74cb55f Improvements to gg.Graph
An empty `Value` is now valid.

It is now possibly to change the edgeVal of an OpenEdge. It feels like
this shouldn't be necessary, but it greatly simplifies the decoding
logic to have this.

A tuple which is created with just one input edge, and with no edge
value of its own, is now automatically simplified to just that input
edge.
2021-12-27 14:22:56 -07:00

258 lines
5.4 KiB
Go

// Package gg implements ginger graph creation, traversal, and (de)serialization
package gg
// Value represents a value being stored in a Graph. No more than one field may
// be non-nil. No fields being set indicates lack of value.
type Value struct {
Name *string
Number *int64
Graph *Graph
// TODO coming soon!
// String *string
}
// Equal returns true if the passed in Value is equivalent.
func (v Value) Equal(v2 Value) bool {
switch {
case v == Value{} && v2 == Value{}:
return true
case v.Name != nil && v2.Name != nil && *v.Name == *v2.Name:
return true
case v.Number != nil && v2.Number != nil && *v.Number == *v2.Number:
return true
case v.Graph != nil && v2.Graph != nil && Equal(v.Graph, v2.Graph):
return true
default:
return false
}
}
// 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"
// TupleVertex is a Vertex which contains two or more in edges and
// exactly one out edge
//
// TODO ^ what about 0 or 1 in edges?
TupleVertex VertexType = "tuple"
)
////////////////////////////////////////////////////////////////////////////////
// OpenEdge is an un-realized Edge which can't be used for anything except
// constructing graphs. It has no meaning on its own.
type OpenEdge struct {
fromV vertex
val Value
}
// WithEdgeVal returns a copy of the OpenEdge with the edge value replaced by
// the given one.
func (oe OpenEdge) WithEdgeVal(val Value) OpenEdge {
oe.val = val
return oe
}
// 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.
func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{fromV: mkVertex(ValueVertex, val), val: edgeVal}
}
// TupleOut creates an OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) coming from the
// TupleVertex comprised of the given ordered-set of input edges.
//
// If len(ins) == 1 and edgeVal == Value{}, then that single OpenEdge is
// returned as-is.
func TupleOut(ins []OpenEdge, edgeVal Value) OpenEdge {
if len(ins) == 1 && edgeVal == (Value{}) {
return ins[0]
}
return OpenEdge{
fromV: mkVertex(TupleVertex, Value{}, ins...),
val: edgeVal,
}
}
func (oe OpenEdge) equal(oe2 OpenEdge) bool {
return oe.val.Equal(oe2.val) && oe.fromV.equal(oe2.fromV)
}
type vertex struct {
VertexType
val Value
tup []OpenEdge
}
func mkVertex(typ VertexType, val Value, tupIns ...OpenEdge) vertex {
return vertex{
VertexType: typ,
val: val,
tup: tupIns,
}
}
func (v vertex) equal(v2 vertex) bool {
if v.VertexType != v2.VertexType {
return false
}
if v.VertexType == ValueVertex {
return v.val.Equal(v2.val)
}
if len(v.tup) != len(v2.tup) {
return false
}
for i := range v.tup {
if !v.tup[i].equal(v2.tup[i]) {
return false
}
}
return true
}
type graphValueIn struct {
val Value
edges []OpenEdge
}
func (valIn graphValueIn) cp() graphValueIn {
cp := valIn
cp.edges = make([]OpenEdge, len(valIn.edges))
copy(cp.edges, valIn.edges)
return valIn
}
func (valIn graphValueIn) equal(valIn2 graphValueIn) bool {
if !valIn.val.Equal(valIn2.val) {
return false
}
if len(valIn.edges) != len(valIn2.edges) {
return false
}
outer:
for _, edge := range valIn.edges {
for _, edge2 := range valIn2.edges {
if edge.equal(edge2) {
continue outer
}
}
return false
}
return true
}
// Graph is an immutable container of a set of vertices. The Graph keeps track
// of all Values which terminate an OpenEdge (which may be a tree of Value/Tuple
// vertices).
//
// NOTE The current implementation of Graph is incredibly inefficient, there's
// lots of O(N) operations, unnecessary copying on changes, and duplicate data
// in memory.
type Graph struct {
valIns []graphValueIn
}
// ZeroGraph is the root empty graph, and is the base off which all graphs are
// built.
var ZeroGraph = &Graph{}
func (g *Graph) cp() *Graph {
cp := &Graph{
valIns: make([]graphValueIn, len(g.valIns)),
}
copy(cp.valIns, g.valIns)
return cp
}
////////////////////////////////////////////////////////////////////////////////
// Graph creation
func (g *Graph) valIn(val Value) graphValueIn {
for _, valIn := range g.valIns {
if valIn.val.Equal(val) {
return valIn
}
}
return graphValueIn{val: val}
}
// AddValueIn takes a OpenEdge and connects it to the Value Vertex containing
// val, returning the new Graph which reflects that connection. Any Vertices
// 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 {
valIn := g.valIn(val)
for _, existingOE := range valIn.edges {
if existingOE.equal(oe) {
return g
}
}
valIn = valIn.cp()
valIn.edges = append(valIn.edges, oe)
g = g.cp()
for i, existingValIn := range g.valIns {
if existingValIn.val.Equal(val) {
g.valIns[i] = valIn
return g
}
}
g.valIns = append(g.valIns, valIn)
return g
}
// Equal returns whether or not the two Graphs are equivalent in value.
func Equal(g1, g2 *Graph) bool {
if len(g1.valIns) != len(g2.valIns) {
return false
}
outer:
for _, valIn1 := range g1.valIns {
for _, valIn2 := range g2.valIns {
if valIn1.equal(valIn2) {
continue outer
}
}
return false
}
return true
}