From 1e30ad695970af15e18858008caa1e0f8968bf93 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 27 Dec 2021 10:11:07 -0700 Subject: [PATCH] Strip out most of the functionality of gg.Graph `gg.Graph` has been reworked in its internal functionality, to more closely match the capability of a purely stack-based implementation. The current implementation is _very_ inefficient, however. Tests have been deliberately left pretty sparse, as I expect the internals to continue to change significantly. --- gg/gg.go | 634 +++++++++++-------------------------------- gg/gg_test.go | 732 ++++++-------------------------------------------- gg/json.go | 186 ------------- 3 files changed, 242 insertions(+), 1310 deletions(-) delete mode 100644 gg/json.go diff --git a/gg/gg.go b/gg/gg.go index 263bdba..9d938c1 100644 --- a/gg/gg.go +++ b/gg/gg.go @@ -1,33 +1,32 @@ // 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. +// Value represents a value being stored in a Graph. Exactly one field must be +// non-nil. type Value struct { - ID string - V interface{} + Name *string + Number *int64 + Graph *Graph + + // TODO coming soon! + // String *string } -// 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, 16) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return Value{ - ID: hex.EncodeToString(b), - V: V, +// Equal returns true if the passed in Value is equivalent. +func (v Value) Equal(v2 Value) bool { + switch { + + 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 } } @@ -46,138 +45,15 @@ const ( TupleVertex VertexType = "tuple" ) -// 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 -} - //////////////////////////////////////////////////////////////////////////////// // 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 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 -// 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. -// -// 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 Tuple 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 - in []OpenEdge -} - -func (v vertex) cp() vertex { - cp := v - cp.in = make([]OpenEdge, len(v.in)) - copy(cp.in, v.in) - return cp -} - -func (v vertex) hasOpenEdge(oe OpenEdge) bool { - oeID := oe.id() - for _, in := range v.in { - if in.id() == oeID { - return true - } - } - return false -} - -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 -} - -// ZeroGraph is the root empty graph, and is the base off which all graphs are -// built. -var ZeroGraph = &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 TupleVertex: - 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 -} - // 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. @@ -203,355 +79,169 @@ func TupleOut(ins []OpenEdge, edgeVal Value) OpenEdge { } } +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 { - to := mkVertex(ValueVertex, val) - 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 + valIn := g.valIn(val) + + for _, existingOE := range valIn.edges { + if existingOE.equal(oe) { + return g + } } - // if the incoming edge already exists in to then there's nothing to do - if to.hasOpenEdge(oe) { - return g - } + valIn = valIn.cp() + valIn.edges = append(valIn.edges, oe) - to = to.cp() - 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) - } + for i, existingValIn := range g.valIns { + if existingValIn.val.Equal(val) { + g.valIns[i] = valIn + return g } } - delete(g.vM, toID) - persist(to) - for _, e := range to.in { - persist(e.fromV) - } + g.valIns = append(g.valIns, valIn) return g } -// 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 -// 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) - 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 - 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 == TupleVertex && 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) - } - - // rmOrphaned descends down the given OpenEdge and removes any Value - // Vertices referenced in it which are now orphaned - 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 == TupleVertex { - for _, juncOe := range oe.fromV.in { - rmOrphaned(juncOe) - } - } - } - 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. -// -// TODO it bothers me that the opposite of Disjoin is Union and not "Join". -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 { - if !v.hasOpenEdge(v2e) { - v.in = append(v.in, v2e) - } - } - } - g.vM[vID] = v - } - return g -} - -// Disjoin splits the Graph into as many independently connected Graphs as it -// contains. Each Graph returned will have vertices connected only within itself -// and not across to the other Graphs, and the Union of all returned Graphs will -// be the original again. -// -// The order of the Graphs returned is not deterministic. (TODO booooooo) -// -// ZeroGraph.Disjoin() returns empty slice. -func (g *Graph) Disjoin() []*Graph { - m := map[string]*Graph{} // maps each id to the Graph it belongs to - mG := map[*Graph]struct{}{} // tracks unique Graphs created - - var connectedTo func(vertex) *Graph - connectedTo = func(v vertex) *Graph { - if v.VertexType == ValueVertex { - if g := m[v.id]; g != nil { - return g - } - } - for _, oe := range v.in { - if g := connectedTo(oe.fromV); g != nil { - return g - } - } - return nil - } - - // used upon finding out that previously-thought-to-be disconnected vertices - // aren't. Merges the two graphs they're connected into one and updates all - // state internal to this function accordingly. - rejoin := func(gDst, gSrc *Graph) { - for id, v := range gSrc.vM { - gDst.vM[id] = v - m[id] = gDst - } - delete(mG, gSrc) - } - - var connectTo func(vertex, *Graph) - connectTo = func(v vertex, g *Graph) { - if v.VertexType == ValueVertex { - if g2, ok := m[v.id]; ok && g != g2 { - rejoin(g, g2) - } - m[v.id] = g - } - for _, oe := range v.in { - connectTo(oe.fromV, g) - } - } - - for id, v := range g.vM { - gV := connectedTo(v) - - // if gV is nil it means this vertex is part of a new Graph which - // nothing else has been connected to yet. - if gV == nil { - gV = ZeroGraph.cp() - mG[gV] = struct{}{} - } - gV.vM[id] = v - - // do this no matter what, because we want to descend in to the in edges - // and mark all of those as being part of this graph too - connectTo(v, gV) - } - - gg := make([]*Graph, 0, len(mG)) - for g := range mG { - gg = append(gg, g) - } - return gg -} - -//////////////////////////////////////////////////////////////////////////////// -// Graph traversal - -func (g *Graph) makeView() { - if g.byVal != nil { - return - } - - 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 tuple's first time being gotten - if !top && (ok || v.VertexType != TupleVertex) { - 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) { + + if len(g1.valIns) != len(g2.valIns) { 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 - // tuple's) - if len(v1.in) != len(v2.in) { - return false - } - for _, in := range v1.in { - if !v2.hasOpenEdge(in) { - return false +outer: + for _, valIn1 := range g1.valIns { + + for _, valIn2 := range g2.valIns { + + if valIn1.equal(valIn2) { + continue outer } } + + return false } + return true } - -// TODO Walk, but by edge -// TODO Walk, but without end. AKA FSM - -// Iter will iterate through the Graph's vertices, calling the callback on every -// Vertex in the Graph once. The vertex order used is non-deterministic. If the -// callback returns false the iteration is stopped. -func (g *Graph) Iter(callback func(*Vertex) bool) { - g.makeView() - if len(g.byVal) == 0 { - return - } - - seen := make(map[*Vertex]bool, len(g.byVal)) - 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 - } - - for _, v := range g.byVal { - 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 -} diff --git a/gg/gg_test.go b/gg/gg_test.go index 3f17163..a33d3cb 100644 --- a/gg/gg_test.go +++ b/gg/gg_test.go @@ -1,665 +1,93 @@ package gg import ( - "fmt" - "sort" - "strings" - . "testing" + "strconv" + "testing" "github.com/stretchr/testify/assert" ) -func edge(val Value, from *Vertex) Edge { - return Edge{Value: val, From: from} -} +func TestEqual(t *testing.T) { -func value(val Value, in ...Edge) *Vertex { - return &Vertex{ - VertexType: ValueVertex, - Value: val, - In: in, + i := func(i int64) Value { + return Value{Number: &i} } -} -func tuple(val Value, in ...Edge) Edge { - return Edge{ - From: &Vertex{ - VertexType: TupleVertex, - In: in, + n := func(n string) Value { + return Value{Name: &n} + } + + tests := []struct { + a, b *Graph + exp bool + }{ + { + a: ZeroGraph, + b: ZeroGraph, + exp: true, }, - Value: val, - } -} - -func assertVertexEqual(t *T, exp, got *Vertex, msgAndArgs ...interface{}) bool { - var assertInner func(*Vertex, *Vertex, map[*Vertex]bool) bool - assertInner = func(exp, got *Vertex, m map[*Vertex]bool) bool { - // if got is already in m then we've already looked at it - if m[got] { - return true - } - m[got] = true - - assert.Equal(t, exp.VertexType, got.VertexType, msgAndArgs...) - assert.Equal(t, exp.Value, got.Value, msgAndArgs...) - if !assert.Len(t, got.In, len(exp.In), msgAndArgs...) { - return false - } - for i := range exp.In { - assertInner(exp.In[i].From, got.In[i].From, m) - assert.Equal(t, exp.In[i].Value, got.In[i].Value, msgAndArgs...) - assert.Equal(t, got, got.In[i].To) - assert.Contains(t, got.In[i].From.Out, got.In[i]) - } - return true - - } - return assertInner(exp, got, map[*Vertex]bool{}) -} - -func assertIter(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{}) { - seen := map[*Vertex]bool{} - var gotVals, gotJuncs int - g.Iter(func(v *Vertex) bool { - assert.NotContains(t, seen, v, msgAndArgs...) - seen[v] = true - if v.VertexType == ValueVertex { - gotVals++ - } else { - gotJuncs++ - } - return true - }) - assert.Equal(t, expVals, gotVals, msgAndArgs...) - assert.Equal(t, expJuncs, gotJuncs, msgAndArgs...) -} - -type graphTest struct { - name string - out func() *Graph - exp []*Vertex - numVals, numJuncs int -} - -func mkTest(name string, out func() *Graph, numVals, numJuncs int, exp ...*Vertex) graphTest { - return graphTest{ - name: name, - out: out, - exp: exp, - numVals: numVals, numJuncs: numJuncs, - } -} - -func TestGraph(t *T) { - var ( - v0 = NewValue("v0") - v1 = NewValue("v1") - v2 = NewValue("v2") - v3 = NewValue("v3") - e0 = NewValue("e0") - e00 = NewValue("e00") - e01 = NewValue("e01") - e1 = NewValue("e1") - e10 = NewValue("e10") - e11 = NewValue("e11") - e2 = NewValue("e2") - e20 = NewValue("e20") - e21 = NewValue("e21") - ej0 = NewValue("ej0") - ej1 = NewValue("ej1") - ej2 = NewValue("ej2") - ) - tests := []graphTest{ - mkTest( - "values-basic", - func() *Graph { - return ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - }, - 2, 0, - value(v0), - value(v1, edge(e0, value(v0))), - ), - - mkTest( - "values-2edges", - func() *Graph { - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) - return g0.AddValueIn(ValueOut(v1, e1), v2) - }, - 3, 0, - value(v0), - value(v1), - value(v2, - edge(e0, value(v0)), - edge(e1, value(v1)), - ), - ), - - mkTest( - "values-separate", - func() *Graph { - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - return g0.AddValueIn(ValueOut(v2, e2), v3) - }, - 4, 0, - value(v0), - value(v1, edge(e0, value(v0))), - value(v2), - value(v3, edge(e2, value(v2))), - ), - - mkTest( - "values-circular", - func() *Graph { - return ZeroGraph.AddValueIn(ValueOut(v0, e0), v0) - }, - 1, 0, - value(v0, edge(e0, value(v0))), - ), - - mkTest( - "values-circular2", - func() *Graph { - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - return g0.AddValueIn(ValueOut(v1, e1), v0) - }, - 2, 0, - value(v0, edge(e1, value(v1, edge(e0, value(v0))))), - value(v1, edge(e0, value(v0, edge(e1, value(v1))))), - ), - - mkTest( - "values-circular3", - func() *Graph { - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - g1 := g0.AddValueIn(ValueOut(v1, e1), v2) - return g1.AddValueIn(ValueOut(v2, e2), v1) - }, - 3, 0, - value(v0), - value(v1, - edge(e0, value(v0)), - edge(e2, value(v2, edge(e1, value(v1)))), - ), - value(v2, edge(e1, value(v1, - edge(e0, value(v0)), - edge(e2, value(v2)), - ))), - ), - - mkTest( - "tuple-basic", - func() *Graph { - e0 := ValueOut(v0, e0) - e1 := ValueOut(v1, e1) - ej0 := TupleOut([]OpenEdge{e0, e1}, ej0) - return ZeroGraph.AddValueIn(ej0, v2) - }, - 3, 1, - value(v0), value(v1), - value(v2, tuple(ej0, - edge(e0, value(v0)), - edge(e1, value(v1)), - )), - ), - - mkTest( - "tuple-basic2", - func() *Graph { - e00 := ValueOut(v0, e00) - e10 := ValueOut(v1, e10) - ej0 := TupleOut([]OpenEdge{e00, e10}, ej0) - e01 := ValueOut(v0, e01) - e11 := ValueOut(v1, e11) - ej1 := TupleOut([]OpenEdge{e01, e11}, ej1) - ej2 := TupleOut([]OpenEdge{ej0, ej1}, ej2) - return ZeroGraph.AddValueIn(ej2, v2) - }, - 3, 3, - value(v0), value(v1), - value(v2, tuple(ej2, - tuple(ej0, - edge(e00, value(v0)), - edge(e10, value(v1)), - ), - tuple(ej1, - edge(e01, value(v0)), - edge(e11, value(v1)), - ), - )), - ), - - mkTest( - "tuple-circular", - func() *Graph { - e0 := ValueOut(v0, e0) - e1 := ValueOut(v1, e1) - ej0 := TupleOut([]OpenEdge{e0, e1}, ej0) - g0 := ZeroGraph.AddValueIn(ej0, v2) - e20 := ValueOut(v2, e20) - g1 := g0.AddValueIn(e20, v0) - e21 := ValueOut(v2, e21) - return g1.AddValueIn(e21, v1) - }, - 3, 1, - value(v0, edge(e20, value(v2, tuple(ej0, - edge(e0, value(v0)), - edge(e1, value(v1, edge(e21, value(v2)))), - )))), - value(v1, edge(e21, value(v2, tuple(ej0, - edge(e0, value(v0, edge(e20, value(v2)))), - edge(e1, value(v1)), - )))), - value(v2, tuple(ej0, - edge(e0, value(v0, edge(e20, value(v2)))), - edge(e1, value(v1, edge(e21, value(v2)))), - )), - ), - } - - for i := range tests { - t.Logf("test[%d]:%q", i, tests[i].name) - out := tests[i].out() - for j, exp := range tests[i].exp { - msgAndArgs := []interface{}{ - "tests[%d].name:%q exp[%d].val:%q", - i, tests[i].name, j, exp.Value.V.(string), - } - v := out.ValueVertex(exp.Value) - if !assert.NotNil(t, v, msgAndArgs...) { - continue - } - assertVertexEqual(t, exp, v, msgAndArgs...) - } - - msgAndArgs := []interface{}{ - "tests[%d].name:%q", - i, tests[i].name, - } - - // sanity check that graphs are equal to themselves - assert.True(t, Equal(out, out), msgAndArgs...) - - // test the Iter method in here too - assertIter(t, tests[i].numVals, tests[i].numJuncs, out, msgAndArgs...) - } -} - -func TestGraphImmutability(t *T) { - v0 := NewValue("v0") - v1 := NewValue("v1") - e0 := NewValue("e0") - oe0 := ValueOut(v0, e0) - g0 := ZeroGraph.AddValueIn(oe0, v1) - assert.Nil(t, ZeroGraph.ValueVertex(v0)) - assert.Nil(t, ZeroGraph.ValueVertex(v1)) - assert.NotNil(t, g0.ValueVertex(v0)) - assert.NotNil(t, g0.ValueVertex(v1)) - - // half-edges should be re-usable - v2 := NewValue("v2") - v3a, v3b := NewValue("v3a"), NewValue("v3b") - e1 := NewValue("e1") - oe1 := ValueOut(v2, e1) - g1a := g0.AddValueIn(oe1, v3a) - g1b := g0.AddValueIn(oe1, v3b) - assertVertexEqual(t, value(v3a, edge(e1, value(v2))), g1a.ValueVertex(v3a)) - assert.Nil(t, g1a.ValueVertex(v3b)) - assertVertexEqual(t, value(v3b, edge(e1, value(v2))), g1b.ValueVertex(v3b)) - assert.Nil(t, g1b.ValueVertex(v3a)) - - // ... even re-usable twice in succession - v3 := NewValue("v3") - v4 := NewValue("v4") - g2 := g0.AddValueIn(oe1, v3).AddValueIn(oe1, v4) - assert.Nil(t, g2.ValueVertex(v3b)) - assert.Nil(t, g2.ValueVertex(v3a)) - assertVertexEqual(t, value(v3, edge(e1, value(v2))), g2.ValueVertex(v3)) - assertVertexEqual(t, value(v4, edge(e1, value(v2))), g2.ValueVertex(v4)) -} - -func TestGraphDelValueIn(t *T) { - v0 := NewValue("v0") - v1 := NewValue("v1") - e0 := NewValue("e0") - { // removing from null - g := ZeroGraph.DelValueIn(ValueOut(v0, e0), v1) - assert.True(t, Equal(ZeroGraph, g)) - } - - e1 := NewValue("e1") - { // removing edge from vertex which doesn't have that edge - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - g1 := g0.DelValueIn(ValueOut(v0, e1), v1) - assert.True(t, Equal(g0, g1)) - } - - { // removing only edge - oe := ValueOut(v0, e0) - g0 := ZeroGraph.AddValueIn(oe, v1) - g1 := g0.DelValueIn(oe, v1) - assert.True(t, Equal(ZeroGraph, g1)) - } - - ej0 := NewValue("ej0") - v2 := NewValue("v2") - { // removing only edge (tuple) - oe := TupleOut([]OpenEdge{ - ValueOut(v0, e0), - ValueOut(v1, e1), - }, ej0) - g0 := ZeroGraph.AddValueIn(oe, v2) - g1 := g0.DelValueIn(oe, v2) - assert.True(t, Equal(ZeroGraph, g1)) - } - - { // removing one of two edges - oe := ValueOut(v1, e0) - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) - g1 := g0.AddValueIn(oe, v2) - g2 := g1.DelValueIn(oe, v2) - assert.True(t, Equal(g0, g2)) - assert.NotNil(t, g2.ValueVertex(v0)) - assert.Nil(t, g2.ValueVertex(v1)) - assert.NotNil(t, g2.ValueVertex(v2)) - } - - e2 := NewValue("e2") - eja, ejb := NewValue("eja"), NewValue("ejb") - v3 := NewValue("v3") - { // removing one of two edges (tuple) - e0 := ValueOut(v0, e0) - e1 := ValueOut(v1, e1) - e2 := ValueOut(v2, e2) - oeA := TupleOut([]OpenEdge{e0, e1}, eja) - oeB := TupleOut([]OpenEdge{e1, e2}, ejb) - g0a := ZeroGraph.AddValueIn(oeA, v3) - g0b := ZeroGraph.AddValueIn(oeB, v3) - g1 := g0a.Union(g0b).DelValueIn(oeA, v3) - assert.True(t, Equal(g1, g0b)) - assert.Nil(t, g1.ValueVertex(v0)) - assert.NotNil(t, g1.ValueVertex(v1)) - assert.NotNil(t, g1.ValueVertex(v2)) - assert.NotNil(t, g1.ValueVertex(v3)) - } - - { // removing one of two edges in circular graph - e0 := ValueOut(v0, e0) - e1 := ValueOut(v1, e1) - g0 := ZeroGraph.AddValueIn(e0, v1).AddValueIn(e1, v0) - g1 := g0.DelValueIn(e0, v1) - assert.True(t, Equal(ZeroGraph.AddValueIn(e1, v0), g1)) - assert.NotNil(t, g1.ValueVertex(v0)) - assert.NotNil(t, g1.ValueVertex(v1)) - } - - ej := NewValue("ej") - { // removing to's only edge, sub-nodes have edge to each other - oej := TupleOut([]OpenEdge{ - ValueOut(v0, ej0), - ValueOut(v1, ej0), - }, ej) - g0 := ZeroGraph.AddValueIn(oej, v2) - e0 := ValueOut(v0, e0) - g1 := g0.AddValueIn(e0, v1) - g2 := g1.DelValueIn(oej, v2) - assert.True(t, Equal(ZeroGraph.AddValueIn(e0, v1), g2)) - assert.NotNil(t, g2.ValueVertex(v0)) - assert.NotNil(t, g2.ValueVertex(v1)) - assert.Nil(t, g2.ValueVertex(v2)) - } -} - -// deterministically hashes a Graph. -func graphStr(g *Graph) string { - var vStr func(vertex) string - var oeStr func(OpenEdge) string - vStr = func(v vertex) string { - if v.VertexType == ValueVertex { - return fmt.Sprintf("v:%q\n", v.val.V.(string)) - } - s := fmt.Sprintf("j:%d\n", len(v.in)) - ssOE := make([]string, len(v.in)) - for i := range v.in { - ssOE[i] = oeStr(v.in[i]) - } - sort.Strings(ssOE) - return s + strings.Join(ssOE, "") - } - oeStr = func(oe OpenEdge) string { - s := fmt.Sprintf("oe:%q\n", oe.val.V.(string)) - return s + vStr(oe.fromV) - } - sVV := make([]string, 0, len(g.vM)) - for _, v := range g.vM { - sVV = append(sVV, vStr(v)) - } - sort.Strings(sVV) - return strings.Join(sVV, "") -} - -func assertEqualSets(t *T, exp, got []*Graph) bool { - if !assert.Equal(t, len(exp), len(got)) { - return false - } - - m := map[*Graph]string{} - for _, g := range exp { - m[g] = graphStr(g) - } - for _, g := range got { - m[g] = graphStr(g) - } - - sort.Slice(exp, func(i, j int) bool { - return m[exp[i]] < m[exp[j]] - }) - sort.Slice(got, func(i, j int) bool { - return m[got[i]] < m[got[j]] - }) - - b := true - for i := range exp { - b = b || assert.True(t, Equal(exp[i], got[i]), "i:%d exp:%q got:%q", i, m[exp[i]], m[got[i]]) - } - return b -} - -func TestGraphUnion(t *T) { - assertUnion := func(g1, g2 *Graph) *Graph { - ga := g1.Union(g2) - gb := g2.Union(g1) - assert.True(t, Equal(ga, gb)) - return ga - } - - assertDisjoin := func(g *Graph, exp ...*Graph) { - ggDisj := g.Disjoin() - assertEqualSets(t, exp, ggDisj) - } - - v0 := NewValue("v0") - v1 := NewValue("v1") - e0 := NewValue("e0") - { // Union with ZeroGraph - assert.True(t, Equal(ZeroGraph, ZeroGraph.Union(ZeroGraph))) - - g := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - assert.True(t, Equal(g, assertUnion(g, ZeroGraph))) - - assertDisjoin(g, g) - } - - v2 := NewValue("v2") - v3 := NewValue("v3") - e1 := NewValue("e1") - { // Two disparate graphs union'd - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - g1 := ZeroGraph.AddValueIn(ValueOut(v2, e1), v3) - g := assertUnion(g0, g1) - assertVertexEqual(t, value(v0), g.ValueVertex(v0)) - assertVertexEqual(t, value(v1, edge(e0, value(v0))), g.ValueVertex(v1)) - assertVertexEqual(t, value(v2), g.ValueVertex(v2)) - assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3)) - - assertDisjoin(g, g0, g1) - } - - va0, vb0 := NewValue("va0"), NewValue("vb0") - va1, vb1 := NewValue("va1"), NewValue("vb1") - va2, vb2 := NewValue("va2"), NewValue("vb2") - ea0, eb0 := NewValue("ea0"), NewValue("eb0") - ea1, eb1 := NewValue("ea1"), NewValue("eb1") - eaj, ebj := NewValue("eaj"), NewValue("ebj") - { // Two disparate graphs with tuples - ga := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ - ValueOut(va0, ea0), - ValueOut(va1, ea1), - }, eaj), va2) - gb := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ - ValueOut(vb0, eb0), - ValueOut(vb1, eb1), - }, ebj), vb2) - g := assertUnion(ga, gb) - assertVertexEqual(t, value(va0), g.ValueVertex(va0)) - assertVertexEqual(t, value(va1), g.ValueVertex(va1)) - assertVertexEqual(t, - value(va2, tuple(eaj, - edge(ea0, value(va0)), - edge(ea1, value(va1)))), - g.ValueVertex(va2), - ) - assertVertexEqual(t, value(vb0), g.ValueVertex(vb0)) - assertVertexEqual(t, value(vb1), g.ValueVertex(vb1)) - assertVertexEqual(t, - value(vb2, tuple(ebj, - edge(eb0, value(vb0)), - edge(eb1, value(vb1)))), - g.ValueVertex(vb2), - ) - - assertDisjoin(g, ga, gb) - } - - { // Two partially overlapping graphs - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) - g1 := ZeroGraph.AddValueIn(ValueOut(v1, e1), v2) - g := assertUnion(g0, g1) - assertVertexEqual(t, value(v0), g.ValueVertex(v0)) - assertVertexEqual(t, value(v1), g.ValueVertex(v1)) - assertVertexEqual(t, - value(v2, - edge(e0, value(v0)), - edge(e1, value(v1)), - ), - g.ValueVertex(v2), - ) - - assertDisjoin(g, g) - } - - ej0 := NewValue("ej0") - ej1 := NewValue("ej1") - { // two partially overlapping graphs with tuples - g0 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ - ValueOut(v0, e0), - ValueOut(v1, e1), - }, ej0), v2) - g1 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ - ValueOut(v0, e0), - ValueOut(v1, e1), - }, ej1), v2) - g := assertUnion(g0, g1) - assertVertexEqual(t, value(v0), g.ValueVertex(v0)) - assertVertexEqual(t, value(v1), g.ValueVertex(v1)) - assertVertexEqual(t, - value(v2, - tuple(ej0, edge(e0, value(v0)), edge(e1, value(v1))), - tuple(ej1, edge(e0, value(v0)), edge(e1, value(v1))), - ), - g.ValueVertex(v2), - ) - - assertDisjoin(g, g) - } - - { // Two equal graphs - g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - g := assertUnion(g0, g0) - assertVertexEqual(t, value(v0), g.ValueVertex(v0)) - assertVertexEqual(t, - value(v1, edge(e0, value(v0))), - g.ValueVertex(v1), - ) - } - - { // Two equal graphs with tuples - g0 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ - ValueOut(v0, e0), - ValueOut(v1, e1), - }, ej0), v2) - g := assertUnion(g0, g0) - assertVertexEqual(t, value(v0), g.ValueVertex(v0)) - assertVertexEqual(t, value(v1), g.ValueVertex(v1)) - assertVertexEqual(t, - value(v2, - tuple(ej0, edge(e0, value(v0)), edge(e1, value(v1))), - ), - g.ValueVertex(v2), - ) - } -} - -func TestGraphEqual(t *T) { - assertEqual := func(g1, g2 *Graph) { - assert.True(t, Equal(g1, g2)) - assert.True(t, Equal(g2, g1)) - } - - assertNotEqual := func(g1, g2 *Graph) { - assert.False(t, Equal(g1, g2)) - assert.False(t, Equal(g2, g1)) - } - - assertEqual(ZeroGraph, ZeroGraph) // duh - - v0 := NewValue("v0") - v1 := NewValue("v1") - v2 := NewValue("v2") - e0 := NewValue("e0") - e1 := NewValue("e1") - e1a, e1b := NewValue("e1a"), NewValue("e1b") - { - // graph is equal to itself, not to null - e0 := ValueOut(v0, e0) - g0 := ZeroGraph.AddValueIn(e0, v1) - assertNotEqual(g0, ZeroGraph) - assertEqual(g0, g0) - - // adding the an existing edge again shouldn't do anything - assertEqual(g0, g0.AddValueIn(e0, v1)) - - // g1a and g1b have the same vertices, but the edges are different - g1a := g0.AddValueIn(ValueOut(v0, e1a), v2) - g1b := g0.AddValueIn(ValueOut(v0, e1b), v2) - assertNotEqual(g1a, g1b) - } - - { // equal construction should yield equality, even if out of order - ga := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) - ga = ga.AddValueIn(ValueOut(v1, e1), v2) - gb := ZeroGraph.AddValueIn(ValueOut(v1, e1), v2) - gb = gb.AddValueIn(ValueOut(v0, e0), v1) - assertEqual(ga, gb) - } - - ej := NewValue("ej") - { // tuple basic test - e0 := ValueOut(v0, e0) - e1 := ValueOut(v1, e1) - ga := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{e0, e1}, ej), v2) - gb := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{e1, e0}, ej), v2) - assertEqual(ga, ga) - assertNotEqual(ga, gb) + { + a: ZeroGraph, + b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + exp: false, + }, + { + a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + exp: true, + }, + { + a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ + ValueOut(n("in"), n("ident")), + ValueOut(i(1), n("ident")), + }, n("add")), n("out")), + exp: false, + }, + { + // tuples are different order + a: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ + ValueOut(i(1), n("ident")), + ValueOut(n("in"), n("ident")), + }, n("add")), n("out")), + b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ + ValueOut(n("in"), n("ident")), + ValueOut(i(1), n("ident")), + }, n("add")), n("out")), + exp: false, + }, + { + a: ZeroGraph. + AddValueIn(ValueOut(n("in"), n("incr")), n("out")). + AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), + b: ZeroGraph. + AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + exp: false, + }, + { + a: ZeroGraph. + AddValueIn(ValueOut(n("in"), n("incr")), n("out")). + AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), + b: ZeroGraph. + AddValueIn(ValueOut(n("in"), n("incr")), n("out")). + AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), + exp: true, + }, + { + // order of value ins shouldn't matter + a: ZeroGraph. + AddValueIn(ValueOut(n("in"), n("incr")), n("out")). + AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), + b: ZeroGraph. + AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")). + AddValueIn(ValueOut(n("in"), n("incr")), n("out")), + exp: true, + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert.Equal(t, test.exp, Equal(test.a, test.b)) + }) } } diff --git a/gg/json.go b/gg/json.go deleted file mode 100644 index 4357e9c..0000000 --- a/gg/json.go +++ /dev/null @@ -1,186 +0,0 @@ -package gg - -import ( - "encoding/json" - "fmt" -) - -type openEdgeJSON struct { - From vertexJSON `json:"from"` - ValueID string `json:"valueID"` -} - -type vertexJSON struct { - Type VertexType `json:"type"` - ValueID string `json:"valueID,omitempty"` - In []openEdgeJSON `json:"in"` -} - -type graphJSON struct { - Values map[string]json.RawMessage `json:"values"` - ValueVertices []vertexJSON `json:"valueVertices"` -} - -// MarshalJSON implements the json.Marshaler interface for a Graph. All Values -// in the Graph will have json.Marshal called on them as-is in order to marshal -// them. -func (g *Graph) MarshalJSON() ([]byte, error) { - gJ := graphJSON{ - Values: map[string]json.RawMessage{}, - ValueVertices: make([]vertexJSON, 0, len(g.vM)), - } - - withVal := func(val Value) (string, error) { - if _, ok := gJ.Values[val.ID]; !ok { - valJ, err := json.Marshal(val.V) - if err != nil { - return "", err - } - gJ.Values[val.ID] = json.RawMessage(valJ) - } - return val.ID, nil - } - - // two locally defined, mutually recursive functions. This kind of thing - // could probably be abstracted out, I feel like it happens frequently with - // graph code. - var mkIns func([]OpenEdge) ([]openEdgeJSON, error) - var mkVert func(vertex) (vertexJSON, error) - - mkIns = func(in []OpenEdge) ([]openEdgeJSON, error) { - inJ := make([]openEdgeJSON, len(in)) - for i := range in { - valID, err := withVal(in[i].val) - if err != nil { - return nil, err - } - vJ, err := mkVert(in[i].fromV) - if err != nil { - return nil, err - } - inJ[i] = openEdgeJSON{From: vJ, ValueID: valID} - } - return inJ, nil - } - - mkVert = func(v vertex) (vertexJSON, error) { - ins, err := mkIns(v.in) - if err != nil { - return vertexJSON{}, err - } - vJ := vertexJSON{ - Type: v.VertexType, - In: ins, - } - if v.VertexType == ValueVertex { - valID, err := withVal(v.val) - if err != nil { - return vJ, err - } - vJ.ValueID = valID - } - return vJ, nil - } - - for _, v := range g.vM { - vJ, err := mkVert(v) - if err != nil { - return nil, err - } - gJ.ValueVertices = append(gJ.ValueVertices, vJ) - } - - return json.Marshal(gJ) -} - -type jsonUnmarshaler struct { - g *Graph - fn func(json.RawMessage) (interface{}, error) -} - -// JSONUnmarshaler returns a json.Unmarshaler instance which, when used, will -// unmarshal a json string into the Graph instance being called on here. -// -// The passed in function is used to unmarshal Values (used in both ValueVertex -// vertices and edges) from json strings into go values. The returned inteface{} -// should have already had the unmarshal from the given json string performed on -// it. -// -// The json.Unmarshaler returned can be used many times, but will reset the -// Graph completely before each use. -func (g *Graph) JSONUnmarshaler(fn func(json.RawMessage) (interface{}, error)) json.Unmarshaler { - return jsonUnmarshaler{g: g, fn: fn} -} - -func (jm jsonUnmarshaler) UnmarshalJSON(b []byte) error { - *(jm.g) = Graph{} - jm.g.vM = map[string]vertex{} - - var gJ graphJSON - if err := json.Unmarshal(b, &gJ); err != nil { - return err - } - - vals := map[string]Value{} - getVal := func(valID string) (Value, error) { - if val, ok := vals[valID]; ok { - return val, nil - } - - j, ok := gJ.Values[valID] - if !ok { - return Value{}, fmt.Errorf("unmarshaling malformed graph, value with ID %q not defined", valID) - } - - V, err := jm.fn(j) - if err != nil { - return Value{}, err - } - - val := Value{ID: valID, V: V} - vals[valID] = val - return val, nil - } - - var mkIns func([]openEdgeJSON) ([]OpenEdge, error) - var mkVert func(vertexJSON) (vertex, error) - - mkIns = func(inJ []openEdgeJSON) ([]OpenEdge, error) { - in := make([]OpenEdge, len(inJ)) - for i := range inJ { - val, err := getVal(inJ[i].ValueID) - if err != nil { - return nil, err - } - v, err := mkVert(inJ[i].From) - if err != nil { - return nil, err - } - in[i] = OpenEdge{fromV: v, val: val} - } - return in, nil - } - - mkVert = func(vJ vertexJSON) (vertex, error) { - ins, err := mkIns(vJ.In) - if err != nil { - return vertex{}, err - } - var val Value - if vJ.Type == ValueVertex { - if val, err = getVal(vJ.ValueID); err != nil { - return vertex{}, err - } - } - return mkVertex(vJ.Type, val, ins...), nil - } - - for _, v := range gJ.ValueVertices { - v, err := mkVert(v) - if err != nil { - return err - } - jm.g.vM[v.id] = v - } - return nil -}