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.
This commit is contained in:
parent
e4bdeb8797
commit
1e30ad6959
634
gg/gg.go
634
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
|
||||
}
|
||||
|
732
gg/gg_test.go
732
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
186
gg/json.go
186
gg/json.go
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user