From e52befb7ed97603ce37da83c4a3c24f1a94bfa4f Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 21 Jan 2018 15:39:25 +0000 Subject: [PATCH] refacctor gg to use Value instead of Identifier, which will make serializing more straightforward, and reduces some complexity of the code besides --- gg/gg.go | 183 ++++++------- gg/gg_test.go | 513 +++++++++++++++++++---------------- gim/box.go | 4 +- gim/constraint/constraint.go | 26 +- gim/main.go | 48 ++-- gim/view.go | 6 +- 6 files changed, 416 insertions(+), 364 deletions(-) diff --git a/gg/gg.go b/gg/gg.go index 92eec62..e0f9800 100644 --- a/gg/gg.go +++ b/gg/gg.go @@ -2,63 +2,62 @@ package gg import ( - "crypto/md5" + "crypto/rand" "encoding/hex" "fmt" - "hash" + "strings" ) -// TODO instead of Identifier being public, make it encoding.TextMarshaler - -// Identifier is implemented by any value which can return a unique string for -// itself via an Identify method -type Identifier interface { - Identify(hash.Hash) +// Value wraps a go value in a way such that it will be uniquely identified +// within any Graph and between Graphs. Use NewValue to create a Value instance. +// You can create an instance manually as long as ID is globally unique. +type Value struct { + ID string + V interface{} } -func identify(i Identifier) string { - h := md5.New() - i.Identify(h) - return hex.EncodeToString(h.Sum(nil)) +// NewValue returns a Value instance wrapping any go value. The Value returned +// will be independent of the passed in go value. So if the same go value is +// passed in twice then the two returned Value instances will be treated as +// being different values by Graph. +func NewValue(V interface{}) Value { + b := make([]byte, 8) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return Value{ + ID: hex.EncodeToString(b), + V: V, + } } -// Str is an Identifier identified by its string value -type Str string - -// Identify implements the Identifier interface -func (s Str) Identify(h hash.Hash) { - fmt.Fprintf(h, "%q", s) -} - -//////////////////////////////////////////////////////////////////////////////// - // VertexType enumerates the different possible vertex types type VertexType string const ( - // Value is a Vertex which contains exactly one value and has at least one - // edge (either input or output) - Value VertexType = "value" + // ValueVertex is a Vertex which contains exactly one value and has at least + // one edge (either input or output) + ValueVertex VertexType = "value" - // Junction is a Vertex which contains two or more in edges and exactly one - // out edge - Junction VertexType = "junction" + // JunctionVertex is a Vertex which contains two or more in edges and + // exactly one out edge + JunctionVertex VertexType = "junction" ) // Edge is a uni-directional connection between two vertices with an attribute // value type Edge struct { From *Vertex - Value Identifier + 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 - ID string // identifier of the vertex, unique within the graph - Value Identifier // Value is valid if-and-only-if VertexType is Value + Value Value // Value is valid if-and-only-if VertexType is ValueVertex In, Out []Edge } @@ -72,14 +71,11 @@ type OpenEdge struct { // already that will need to be taken into account when persisting into the // graph fromV vertex - val Identifier + val Value } -// Identify implements the Identifier interface -func (oe OpenEdge) Identify(h hash.Hash) { - fmt.Fprintln(h, "openEdge") - oe.fromV.Identify(h) - oe.val.Identify(h) +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 @@ -95,28 +91,12 @@ func (oe OpenEdge) Identify(h hash.Hash) { // 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 Identifier + val Value in []OpenEdge } -// A Value vertex is unique by the value it contains -// A Junction vertex is unique by its input edges -func (v vertex) Identify(h hash.Hash) { - switch v.VertexType { - case Value: - fmt.Fprintln(h, "value") - v.val.Identify(h) - case Junction: - fmt.Fprintf(h, "junction:%d\n", len(v.in)) - for _, in := range v.in { - in.Identify(h) - } - default: - panic(fmt.Sprintf("invalid VertexType:%#v", v)) - } -} - func (v vertex) cp() vertex { cp := v cp.in = make([]OpenEdge, len(v.in)) @@ -125,9 +105,9 @@ func (v vertex) cp() vertex { } func (v vertex) hasOpenEdge(oe OpenEdge) bool { - oeID := identify(oe) + oeID := oe.id() for _, in := range v.in { - if identify(in) == oeID { + if in.id() == oeID { return true } } @@ -135,9 +115,9 @@ func (v vertex) hasOpenEdge(oe OpenEdge) bool { } func (v vertex) cpAndDelOpenEdge(oe OpenEdge) (vertex, bool) { - oeID := identify(oe) + oeID := oe.id() for i, in := range v.in { - if identify(in) == oeID { + if in.id() == oeID { v = v.cp() v.in = append(v.in[:i], v.in[i+1:]...) return v, true @@ -168,8 +148,8 @@ func (g *Graph) cp() *Graph { cp := &Graph{ vM: make(map[string]vertex, len(g.vM)), } - for id, v := range g.vM { - cp.vM[id] = v + for vID, v := range g.vM { + cp.vM[vID] = v } return cp } @@ -178,16 +158,17 @@ func (g *Graph) cp() *Graph { // Graph creation // ValueOut creates a OpenEdge which, when used to construct a Graph, represents -// an edge (with edgeVal attached to it) leaving the Value Vertex containing +// an edge (with edgeVal attached to it) coming from the ValueVertex containing // val. // -// When constructing Graphs Value vertices are de-duplicated on their value. So +// When constructing Graphs, Value vertices are de-duplicated on their Value. So // multiple ValueOut OpenEdges constructed with the same val will be leaving the // same Vertex instance in the constructed Graph. -func ValueOut(val, edgeVal Identifier) OpenEdge { +func ValueOut(val, edgeVal Value) OpenEdge { return OpenEdge{ fromV: vertex{ - VertexType: Value, + id: val.ID, + VertexType: ValueVertex, val: val, }, val: edgeVal, @@ -195,16 +176,21 @@ func ValueOut(val, edgeVal Identifier) OpenEdge { } // JunctionOut creates a OpenEdge which, when used to construct a Graph, -// represents an edge (with edgeVal attached to it) leaving the Junction Vertex -// comprised of the given ordered-set of input edges. +// represents an edge (with edgeVal attached to it) coming from the +// JunctionVertex comprised of the given ordered-set of input edges. // // When constructing Graphs Junction vertices are de-duplicated on their input // edges. So multiple Junction OpenEdges constructed with the same set of input // edges will be leaving the same Junction instance in the constructed Graph. -func JunctionOut(in []OpenEdge, edgeVal Identifier) OpenEdge { +func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge { + inIDs := make([]string, len(in)) + for i := range in { + inIDs[i] = in[i].id() + } return OpenEdge{ fromV: vertex{ - VertexType: Junction, + id: "[" + strings.Join(inIDs, ",") + "]", + VertexType: JunctionVertex, in: in, }, val: edgeVal, @@ -215,12 +201,13 @@ func JunctionOut(in []OpenEdge, edgeVal Identifier) OpenEdge { // 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 Identifier) *Graph { +func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph { to := vertex{ - VertexType: Value, + id: val.ID, + VertexType: ValueVertex, val: val, } - toID := identify(to) + toID := to.id // if to is already in the graph, pull it out, as it might have existing in // edges we want to keep @@ -241,8 +228,8 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Identifier) *Graph { // recursively add in any vertices which aren't already there var persist func(vertex) persist = func(v vertex) { - vID := identify(v) - if v.VertexType == Value { + if v.VertexType == ValueVertex { + vID := v.id if _, ok := g.vM[vID]; !ok { g.vM[vID] = v } @@ -266,12 +253,13 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Identifier) *Graph { // 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 Identifier) *Graph { +func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph { to := vertex{ - VertexType: Value, + id: val.ID, + VertexType: ValueVertex, val: val, } - toID := identify(to) + toID := to.id // pull to out of the graph. if it's not there then bail var ok bool @@ -293,9 +281,9 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph { var connectedTo func(string, vertex) bool connectedTo = func(vID string, curr vertex) bool { for _, in := range curr.in { - if in.fromV.VertexType == Value && identify(in.fromV) == vID { + if in.fromV.VertexType == ValueVertex && in.fromV.id == vID { return true - } else if in.fromV.VertexType == Junction && connectedTo(vID, in.fromV) { + } else if in.fromV.VertexType == JunctionVertex && connectedTo(vID, in.fromV) { return true } } @@ -305,7 +293,7 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph { // isOrphaned returns whether the given vertex has any connections to other // nodes in the graph isOrphaned := func(v vertex) bool { - vID := identify(v) + vID := v.id if v, ok := g.vM[vID]; ok && len(v.in) > 0 { return false } @@ -328,11 +316,11 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph { // Vertices referenced in it which are now orphaned var rmOrphaned func(OpenEdge) rmOrphaned = func(oe OpenEdge) { - if oe.fromV.VertexType == Value && isOrphaned(oe.fromV) { - delete(g.vM, identify(oe.fromV)) - } else if oe.fromV.VertexType == Junction { - for _, juncHe := range oe.fromV.in { - rmOrphaned(juncHe) + if oe.fromV.VertexType == ValueVertex && isOrphaned(oe.fromV) { + delete(g.vM, oe.fromV.id) + } else if oe.fromV.VertexType == JunctionVertex { + for _, juncOe := range oe.fromV.in { + rmOrphaned(juncOe) } } } @@ -377,18 +365,17 @@ func (g *Graph) makeView() { var getV func(vertex, bool) *Vertex getV = func(v vertex, top bool) *Vertex { - vID := identify(v) - V, ok := g.all[vID] + V, ok := g.all[v.id] if !ok { - V = &Vertex{VertexType: v.VertexType, ID: vID, Value: v.val} - g.all[vID] = V + V = &Vertex{ID: v.id, VertexType: v.VertexType, Value: v.val} + g.all[v.id] = V } // we can be sure all Value vertices will be called with top==true at // some point, so we only need to descend into the input edges if: // * top is true // * this is a junction's first time being gotten - if !top && (ok || v.VertexType != Junction) { + if !top && (ok || v.VertexType != JunctionVertex) { return V } @@ -400,8 +387,8 @@ func (g *Graph) makeView() { V.In = append(V.In, e) } - if v.VertexType == Value { - g.byVal[identify(v.val)] = V + if v.VertexType == ValueVertex { + g.byVal[v.val.ID] = V } return V @@ -412,15 +399,15 @@ func (g *Graph) makeView() { } } -// Value 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) Value(val Identifier) *Vertex { +// 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[identify(val)] + return g.byVal[val.ID] } -// Values returns all Value Vertices in the Graph -func (g *Graph) Values() []*Vertex { +// 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 { diff --git a/gg/gg_test.go b/gg/gg_test.go index d226bd1..8b35f93 100644 --- a/gg/gg_test.go +++ b/gg/gg_test.go @@ -1,44 +1,30 @@ package gg import ( - "fmt" - "hash" . "testing" "github.com/stretchr/testify/assert" ) -type idAny struct { - i interface{} +func edge(val Value, from *Vertex) Edge { + return Edge{Value: val, From: from} } -func (i idAny) Identify(h hash.Hash) { - fmt.Fprintln(h, i) -} - -func id(i interface{}) Identifier { - return idAny{i: i} -} - -func edge(val string, from *Vertex) Edge { - return Edge{Value: id(val), From: from} -} - -func value(val string, in ...Edge) *Vertex { +func value(val Value, in ...Edge) *Vertex { return &Vertex{ - VertexType: Value, - Value: id(val), + VertexType: ValueVertex, + Value: val, In: in, } } -func junction(val string, in ...Edge) Edge { +func junction(val Value, in ...Edge) Edge { return Edge{ From: &Vertex{ - VertexType: Junction, + VertexType: JunctionVertex, In: in, }, - Value: id(val), + Value: val, } } @@ -74,7 +60,7 @@ func assertWalk(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{} g.Walk(nil, func(v *Vertex) bool { assert.NotContains(t, seen, v, msgAndArgs...) seen[v] = true - if v.VertexType == Value { + if v.VertexType == ValueVertex { gotVals++ } else { gotJuncs++ @@ -102,122 +88,140 @@ func mkTest(name string, out func() *Graph, numVals, numJuncs int, exp ...*Verte } 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 Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + return Null.AddValueIn(ValueOut(v0, e0), v1) }, 2, 0, - value("v0"), - value("v1", edge("e0", value("v0"))), + value(v0), + value(v1, edge(e0, value(v0))), ), mkTest( "values-2edges", func() *Graph { - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) - return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) + g0 := Null.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")), + value(v0), + value(v1), + value(v2, + edge(e0, value(v0)), + edge(e1, value(v1)), ), ), mkTest( "values-separate", func() *Graph { - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - return g0.AddValueIn(ValueOut(id("v2"), id("e2")), id("v3")) + g0 := Null.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"))), + value(v0), + value(v1, edge(e0, value(v0))), + value(v2), + value(v3, edge(e2, value(v2))), ), mkTest( "values-circular", func() *Graph { - return Null.AddValueIn(ValueOut(id("v0"), id("e")), id("v0")) + return Null.AddValueIn(ValueOut(v0, e0), v0) }, 1, 0, - value("v0", edge("e", value("v0"))), + value(v0, edge(e0, value(v0))), ), mkTest( "values-circular2", func() *Graph { - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v0")) + g0 := Null.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"))))), + 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 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - g1 := g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - return g1.AddValueIn(ValueOut(id("v2"), id("e2")), id("v1")) + g0 := Null.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(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")), + value(v2, edge(e1, value(v1, + edge(e0, value(v0)), + edge(e2, value(v2)), ))), ), mkTest( "junction-basic", func() *Graph { - e0 := ValueOut(id("v0"), id("e0")) - e1 := ValueOut(id("v1"), id("e1")) - ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0")) - return Null.AddValueIn(ej0, id("v2")) + e0 := ValueOut(v0, e0) + e1 := ValueOut(v1, e1) + ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0) + return Null.AddValueIn(ej0, v2) }, 3, 1, - value("v0"), value("v1"), - value("v2", junction("ej0", - edge("e0", value("v0")), - edge("e1", value("v1")), + value(v0), value(v1), + value(v2, junction(ej0, + edge(e0, value(v0)), + edge(e1, value(v1)), )), ), mkTest( "junction-basic2", func() *Graph { - e00 := ValueOut(id("v0"), id("e00")) - e10 := ValueOut(id("v1"), id("e10")) - ej0 := JunctionOut([]OpenEdge{e00, e10}, id("ej0")) - e01 := ValueOut(id("v0"), id("e01")) - e11 := ValueOut(id("v1"), id("e11")) - ej1 := JunctionOut([]OpenEdge{e01, e11}, id("ej1")) - ej2 := JunctionOut([]OpenEdge{ej0, ej1}, id("ej2")) - return Null.AddValueIn(ej2, id("v2")) + e00 := ValueOut(v0, e00) + e10 := ValueOut(v1, e10) + ej0 := JunctionOut([]OpenEdge{e00, e10}, ej0) + e01 := ValueOut(v0, e01) + e11 := ValueOut(v1, e11) + ej1 := JunctionOut([]OpenEdge{e01, e11}, ej1) + ej2 := JunctionOut([]OpenEdge{ej0, ej1}, ej2) + return Null.AddValueIn(ej2, v2) }, 3, 3, - value("v0"), value("v1"), - value("v2", junction("ej2", - junction("ej0", - edge("e00", value("v0")), - edge("e10", value("v1")), + value(v0), value(v1), + value(v2, junction(ej2, + junction(ej0, + edge(e00, value(v0)), + edge(e10, value(v1)), ), - junction("ej1", - edge("e01", value("v0")), - edge("e11", value("v1")), + junction(ej1, + edge(e01, value(v0)), + edge(e11, value(v1)), ), )), ), @@ -225,27 +229,27 @@ func TestGraph(t *T) { mkTest( "junction-circular", func() *Graph { - e0 := ValueOut(id("v0"), id("e0")) - e1 := ValueOut(id("v1"), id("e1")) - ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0")) - g0 := Null.AddValueIn(ej0, id("v2")) - e20 := ValueOut(id("v2"), id("e20")) - g1 := g0.AddValueIn(e20, id("v0")) - e21 := ValueOut(id("v2"), id("e21")) - return g1.AddValueIn(e21, id("v1")) + e0 := ValueOut(v0, e0) + e1 := ValueOut(v1, e1) + ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0) + g0 := Null.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", junction("ej0", - edge("e0", value("v0")), - edge("e1", value("v1", edge("e21", value("v2")))), + value(v0, edge(e20, value(v2, junction(ej0, + edge(e0, value(v0)), + edge(e1, value(v1, edge(e21, value(v2)))), )))), - value("v1", edge("e21", value("v2", junction("ej0", - edge("e0", value("v0", edge("e20", value("v2")))), - edge("e1", value("v1")), + value(v1, edge(e21, value(v2, junction(ej0, + edge(e0, value(v0, edge(e20, value(v2)))), + edge(e1, value(v1)), )))), - value("v2", junction("ej0", - edge("e0", value("v0", edge("e20", value("v2")))), - edge("e1", value("v1", edge("e21", value("v2")))), + value(v2, junction(ej0, + edge(e0, value(v0, edge(e20, value(v2)))), + edge(e1, value(v1, edge(e21, value(v2)))), )), ), } @@ -256,9 +260,9 @@ func TestGraph(t *T) { for j, exp := range tests[i].exp { msgAndArgs := []interface{}{ "tests[%d].name:%q exp[%d].val:%q", - i, tests[i].name, j, exp.Value.(idAny).i, + i, tests[i].name, j, exp.Value.V.(string), } - v := out.Value(exp.Value) + v := out.ValueVertex(exp.Value) if !assert.NotNil(t, v, msgAndArgs...) { continue } @@ -279,109 +283,127 @@ func TestGraph(t *T) { } func TestGraphImmutability(t *T) { - e0 := ValueOut(id("v0"), id("e0")) - g0 := Null.AddValueIn(e0, id("v1")) - assert.Nil(t, Null.Value(id("v0"))) - assert.Nil(t, Null.Value(id("v1"))) - assert.NotNil(t, g0.Value(id("v0"))) - assert.NotNil(t, g0.Value(id("v1"))) + v0 := NewValue("v0") + v1 := NewValue("v1") + e0 := NewValue("e0") + oe0 := ValueOut(v0, e0) + g0 := Null.AddValueIn(oe0, v1) + assert.Nil(t, Null.ValueVertex(v0)) + assert.Nil(t, Null.ValueVertex(v1)) + assert.NotNil(t, g0.ValueVertex(v0)) + assert.NotNil(t, g0.ValueVertex(v1)) // half-edges should be re-usable - e1 := ValueOut(id("v2"), id("e1")) - g1a := g0.AddValueIn(e1, id("v3a")) - g1b := g0.AddValueIn(e1, id("v3b")) - assertVertexEqual(t, value("v3a", edge("e1", value("v2"))), g1a.Value(id("v3a"))) - assert.Nil(t, g1a.Value(id("v3b"))) - assertVertexEqual(t, value("v3b", edge("e1", value("v2"))), g1b.Value(id("v3b"))) - assert.Nil(t, g1b.Value(id("v3a"))) + 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 - g2 := g0.AddValueIn(e1, id("v3")).AddValueIn(e1, id("v4")) - assert.Nil(t, g2.Value(id("v3b"))) - assert.Nil(t, g2.Value(id("v3a"))) - assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g2.Value(id("v3"))) - assertVertexEqual(t, value("v4", edge("e1", value("v2"))), g2.Value(id("v4"))) + 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 := Null.DelValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + g := Null.DelValueIn(ValueOut(v0, e0), v1) assert.True(t, Equal(Null, g)) } + e1 := NewValue("e1") { // removing edge from vertex which doesn't have that edge - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - g1 := g0.DelValueIn(ValueOut(id("v0"), id("e1")), id("v1")) + g0 := Null.AddValueIn(ValueOut(v0, e0), v1) + g1 := g0.DelValueIn(ValueOut(v0, e1), v1) assert.True(t, Equal(g0, g1)) } { // removing only edge - oe := ValueOut(id("v0"), id("e0")) - g0 := Null.AddValueIn(oe, id("v1")) - g1 := g0.DelValueIn(oe, id("v1")) + oe := ValueOut(v0, e0) + g0 := Null.AddValueIn(oe, v1) + g1 := g0.DelValueIn(oe, v1) assert.True(t, Equal(Null, g1)) } + ej0 := NewValue("ej0") + v2 := NewValue("v2") { // removing only edge (junction) oe := JunctionOut([]OpenEdge{ - ValueOut(id("v0"), id("e0")), - ValueOut(id("v1"), id("e1")), - }, id("ej0")) - g0 := Null.AddValueIn(oe, id("v2")) - g1 := g0.DelValueIn(oe, id("v2")) + ValueOut(v0, e0), + ValueOut(v1, e1), + }, ej0) + g0 := Null.AddValueIn(oe, v2) + g1 := g0.DelValueIn(oe, v2) assert.True(t, Equal(Null, g1)) } { // removing one of two edges - oe := ValueOut(id("v1"), id("e0")) - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) - g1 := g0.AddValueIn(oe, id("v2")) - g2 := g1.DelValueIn(oe, id("v2")) + oe := ValueOut(v1, e0) + g0 := Null.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.Value(id("v0"))) - assert.Nil(t, g2.Value(id("v1"))) - assert.NotNil(t, g2.Value(id("v2"))) + 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 (junction) - e0 := ValueOut(id("v0"), id("e0")) - e1 := ValueOut(id("v1"), id("e1")) - e2 := ValueOut(id("v2"), id("e2")) - oeA := JunctionOut([]OpenEdge{e0, e1}, id("oeA")) - oeB := JunctionOut([]OpenEdge{e1, e2}, id("oeB")) - g0a := Null.AddValueIn(oeA, id("v3")) - g0b := Null.AddValueIn(oeB, id("v3")) - g1 := g0a.Union(g0b).DelValueIn(oeA, id("v3")) + e0 := ValueOut(v0, e0) + e1 := ValueOut(v1, e1) + e2 := ValueOut(v2, e2) + oeA := JunctionOut([]OpenEdge{e0, e1}, eja) + oeB := JunctionOut([]OpenEdge{e1, e2}, ejb) + g0a := Null.AddValueIn(oeA, v3) + g0b := Null.AddValueIn(oeB, v3) + g1 := g0a.Union(g0b).DelValueIn(oeA, v3) assert.True(t, Equal(g1, g0b)) - assert.Nil(t, g1.Value(id("v0"))) - assert.NotNil(t, g1.Value(id("v1"))) - assert.NotNil(t, g1.Value(id("v2"))) - assert.NotNil(t, g1.Value(id("v3"))) + 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(id("v0"), id("e0")) - e1 := ValueOut(id("v1"), id("e1")) - g0 := Null.AddValueIn(e0, id("v1")).AddValueIn(e1, id("v0")) - g1 := g0.DelValueIn(e0, id("v1")) - assert.True(t, Equal(Null.AddValueIn(e1, id("v0")), g1)) - assert.NotNil(t, g1.Value(id("v0"))) - assert.NotNil(t, g1.Value(id("v1"))) + e0 := ValueOut(v0, e0) + e1 := ValueOut(v1, e1) + g0 := Null.AddValueIn(e0, v1).AddValueIn(e1, v0) + g1 := g0.DelValueIn(e0, v1) + assert.True(t, Equal(Null.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 - ej := JunctionOut([]OpenEdge{ - ValueOut(id("v0"), id("ej0")), - ValueOut(id("v1"), id("ej0")), - }, id("ej")) - g0 := Null.AddValueIn(ej, id("v2")) - e0 := ValueOut(id("v0"), id("e0")) - g1 := g0.AddValueIn(e0, id("v1")) - g2 := g1.DelValueIn(ej, id("v2")) - assert.True(t, Equal(Null.AddValueIn(e0, id("v1")), g2)) - assert.NotNil(t, g2.Value(id("v0"))) - assert.NotNil(t, g2.Value(id("v1"))) - assert.Nil(t, g2.Value(id("v2"))) + oej := JunctionOut([]OpenEdge{ + ValueOut(v0, ej0), + ValueOut(v1, ej0), + }, ej) + g0 := Null.AddValueIn(oej, v2) + e0 := ValueOut(v0, e0) + g1 := g0.AddValueIn(e0, v1) + g2 := g1.DelValueIn(oej, v2) + assert.True(t, Equal(Null.AddValueIn(e0, v1), g2)) + assert.NotNil(t, g2.ValueVertex(v0)) + assert.NotNil(t, g2.ValueVertex(v1)) + assert.Nil(t, g2.ValueVertex(v2)) } } @@ -393,110 +415,124 @@ func TestGraphUnion(t *T) { return ga } + v0 := NewValue("v0") + v1 := NewValue("v1") + e0 := NewValue("e0") { // Union with Null assert.True(t, Equal(Null, Null.Union(Null))) - g := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + g := Null.AddValueIn(ValueOut(v0, e0), v1) assert.True(t, Equal(g, assertUnion(g, Null))) } + v2 := NewValue("v2") + v3 := NewValue("v3") + e1 := NewValue("e1") { // Two disparate graphs union'd - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - g1 := Null.AddValueIn(ValueOut(id("v2"), id("e1")), id("v3")) + g0 := Null.AddValueIn(ValueOut(v0, e0), v1) + g1 := Null.AddValueIn(ValueOut(v2, e1), v3) g := assertUnion(g0, g1) - assertVertexEqual(t, value("v0"), g.Value(id("v0"))) - assertVertexEqual(t, value("v1", edge("e0", value("v0"))), g.Value(id("v1"))) - assertVertexEqual(t, value("v2"), g.Value(id("v2"))) - assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g.Value(id("v3"))) + 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)) } + 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 junctions ga := Null.AddValueIn(JunctionOut([]OpenEdge{ - ValueOut(id("va0"), id("ea0")), - ValueOut(id("va1"), id("ea1")), - }, id("eaj")), id("va2")) + ValueOut(va0, ea0), + ValueOut(va1, ea1), + }, eaj), va2) gb := Null.AddValueIn(JunctionOut([]OpenEdge{ - ValueOut(id("vb0"), id("eb0")), - ValueOut(id("vb1"), id("eb1")), - }, id("ebj")), id("vb2")) + ValueOut(vb0, eb0), + ValueOut(vb1, eb1), + }, ebj), vb2) g := assertUnion(ga, gb) - assertVertexEqual(t, value("va0"), g.Value(id("va0"))) - assertVertexEqual(t, value("va1"), g.Value(id("va1"))) + assertVertexEqual(t, value(va0), g.ValueVertex(va0)) + assertVertexEqual(t, value(va1), g.ValueVertex(va1)) assertVertexEqual(t, - value("va2", junction("eaj", - edge("ea0", value("va0")), - edge("ea1", value("va1")))), - g.Value(id("va2")), + value(va2, junction(eaj, + edge(ea0, value(va0)), + edge(ea1, value(va1)))), + g.ValueVertex(va2), ) - assertVertexEqual(t, value("vb0"), g.Value(id("vb0"))) - assertVertexEqual(t, value("vb1"), g.Value(id("vb1"))) + assertVertexEqual(t, value(vb0), g.ValueVertex(vb0)) + assertVertexEqual(t, value(vb1), g.ValueVertex(vb1)) assertVertexEqual(t, - value("vb2", junction("ebj", - edge("eb0", value("vb0")), - edge("eb1", value("vb1")))), - g.Value(id("vb2")), + value(vb2, junction(ebj, + edge(eb0, value(vb0)), + edge(eb1, value(vb1)))), + g.ValueVertex(vb2), ) } { // Two partially overlapping graphs - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) - g1 := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) + g0 := Null.AddValueIn(ValueOut(v0, e0), v2) + g1 := Null.AddValueIn(ValueOut(v1, e1), v2) g := assertUnion(g0, g1) - assertVertexEqual(t, value("v0"), g.Value(id("v0"))) - assertVertexEqual(t, value("v1"), g.Value(id("v1"))) + 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")), + value(v2, + edge(e0, value(v0)), + edge(e1, value(v1)), ), - g.Value(id("v2")), + g.ValueVertex(v2), ) } + ej0 := NewValue("ej0") + ej1 := NewValue("ej1") { // two partially overlapping graphs with junctions g0 := Null.AddValueIn(JunctionOut([]OpenEdge{ - ValueOut(id("v0"), id("e0")), - ValueOut(id("v1"), id("e1")), - }, id("ej0")), id("v2")) + ValueOut(v0, e0), + ValueOut(v1, e1), + }, ej0), v2) g1 := Null.AddValueIn(JunctionOut([]OpenEdge{ - ValueOut(id("v0"), id("e0")), - ValueOut(id("v1"), id("e1")), - }, id("ej1")), id("v2")) + ValueOut(v0, e0), + ValueOut(v1, e1), + }, ej1), v2) g := assertUnion(g0, g1) - assertVertexEqual(t, value("v0"), g.Value(id("v0"))) - assertVertexEqual(t, value("v1"), g.Value(id("v1"))) + assertVertexEqual(t, value(v0), g.ValueVertex(v0)) + assertVertexEqual(t, value(v1), g.ValueVertex(v1)) assertVertexEqual(t, - value("v2", - junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))), - junction("ej1", edge("e0", value("v0")), edge("e1", value("v1"))), + value(v2, + junction(ej0, edge(e0, value(v0)), edge(e1, value(v1))), + junction(ej1, edge(e0, value(v0)), edge(e1, value(v1))), ), - g.Value(id("v2")), + g.ValueVertex(v2), ) } { // Two equal graphs - g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + g0 := Null.AddValueIn(ValueOut(v0, e0), v1) g := assertUnion(g0, g0) - assertVertexEqual(t, value("v0"), g.Value(id("v0"))) + assertVertexEqual(t, value(v0), g.ValueVertex(v0)) assertVertexEqual(t, - value("v1", edge("e0", value("v0"))), - g.Value(id("v1")), + value(v1, edge(e0, value(v0))), + g.ValueVertex(v1), ) } { // Two equal graphs with junctions g0 := Null.AddValueIn(JunctionOut([]OpenEdge{ - ValueOut(id("v0"), id("e0")), - ValueOut(id("v1"), id("e1")), - }, id("ej0")), id("v2")) + ValueOut(v0, e0), + ValueOut(v1, e1), + }, ej0), v2) g := assertUnion(g0, g0) - assertVertexEqual(t, value("v0"), g.Value(id("v0"))) - assertVertexEqual(t, value("v1"), g.Value(id("v1"))) + assertVertexEqual(t, value(v0), g.ValueVertex(v0)) + assertVertexEqual(t, value(v1), g.ValueVertex(v1)) assertVertexEqual(t, - value("v2", - junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))), + value(v2, + junction(ej0, edge(e0, value(v0)), edge(e1, value(v1))), ), - g.Value(id("v2")), + g.ValueVertex(v2), ) } } @@ -514,35 +550,42 @@ func TestGraphEqual(t *T) { assertEqual(Null, Null) // 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(id("v0"), id("e0")) - g0 := Null.AddValueIn(e0, id("v1")) + e0 := ValueOut(v0, e0) + g0 := Null.AddValueIn(e0, v1) assertNotEqual(g0, Null) assertEqual(g0, g0) // adding the an existing edge again shouldn't do anything - assertEqual(g0, g0.AddValueIn(e0, id("v1"))) + assertEqual(g0, g0.AddValueIn(e0, v1)) // g1a and g1b have the same vertices, but the edges are different - g1a := g0.AddValueIn(ValueOut(id("v0"), id("e1a")), id("v2")) - g1b := g0.AddValueIn(ValueOut(id("v0"), id("e1b")), id("v2")) + 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 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - ga = ga.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - gb := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - gb = gb.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + ga := Null.AddValueIn(ValueOut(v0, e0), v1) + ga = ga.AddValueIn(ValueOut(v1, e1), v2) + gb := Null.AddValueIn(ValueOut(v1, e1), v2) + gb = gb.AddValueIn(ValueOut(v0, e0), v1) assertEqual(ga, gb) } + ej := NewValue("ej") { // junction basic test - e0 := ValueOut(id("v0"), id("e0")) - e1 := ValueOut(id("v1"), id("e1")) - ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, id("ej")), id("v2")) - gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, id("ej")), id("v2")) + e0 := ValueOut(v0, e0) + e1 := ValueOut(v1, e1) + ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, ej), v2) + gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, ej), v2) assertEqual(ga, ga) assertNotEqual(ga, gb) } diff --git a/gim/box.go b/gim/box.go index 6036d6a..4c69257 100644 --- a/gim/box.go +++ b/gim/box.go @@ -42,8 +42,8 @@ func boxFromVertex(v *gg.Vertex, flowDir geo.XY) box { numIn: len(v.In), numOut: len(v.Out), } - if v.VertexType == gg.Value { - b.body = string(v.Value.(gg.Str)) + if v.VertexType == gg.ValueVertex { + b.body = v.Value.V.(string) } return b } diff --git a/gim/constraint/constraint.go b/gim/constraint/constraint.go index f0c7d20..7242350 100644 --- a/gim/constraint/constraint.go +++ b/gim/constraint/constraint.go @@ -17,28 +17,38 @@ type Constraint struct { LT string } -const ltEdge = gg.Str("lt") +var ltEdge = gg.NewValue("lt") // Engine processes sets of constraints to generate an output type Engine struct { - g *gg.Graph + g *gg.Graph + vals map[string]gg.Value } // NewEngine initializes and returns an empty Engine func NewEngine() *Engine { - return &Engine{g: gg.Null} + return &Engine{g: gg.Null, vals: map[string]gg.Value{}} +} + +func (e *Engine) getVal(elem string) gg.Value { + if val, ok := e.vals[elem]; ok { + return val + } + val := gg.NewValue(elem) + e.vals[elem] = val + return val } // AddConstraint adds the given constraint to the engine's set, returns false if // the constraint couldn't be added due to a conflict with a previous constraint func (e *Engine) AddConstraint(c Constraint) bool { - elem := gg.Str(c.Elem) - g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), gg.Str(c.LT)) + elem := e.getVal(c.Elem) + g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), e.getVal(c.LT)) // Check for loops in g starting at c.Elem, bail if there are any { seen := map[*gg.Vertex]bool{} - start := g.Value(elem) + start := g.ValueVertex(elem) var hasLoop func(v *gg.Vertex) bool hasLoop = func(v *gg.Vertex) bool { if seen[v] { @@ -66,12 +76,12 @@ func (e *Engine) AddConstraint(c Constraint) bool { // engine and whose value is known to be zero. func (e *Engine) Solve() map[string]int { m := map[string]int{} - if len(e.g.Values()) == 0 { + if len(e.g.ValueVertices()) == 0 { return m } vElem := func(v *gg.Vertex) string { - return string(v.Value.(gg.Str)) + return v.Value.V.(string) } // first the roots are determined to be the elements with no In edges, which diff --git a/gim/main.go b/gim/main.go index 639ec70..9a1698a 100644 --- a/gim/main.go +++ b/gim/main.go @@ -35,25 +35,36 @@ func debugf(str string, args ...interface{}) { fmt.Fprintf(os.Stderr, str, args...) } -func mkGraph() *gg.Graph { - aE0 := gg.ValueOut(gg.Str("a"), gg.Str("aE0")) - aE1 := gg.ValueOut(gg.Str("a"), gg.Str("aE1")) - aE2 := gg.ValueOut(gg.Str("a"), gg.Str("aE2")) - aE3 := gg.ValueOut(gg.Str("a"), gg.Str("aE3")) +func mkGraph() (*gg.Graph, gg.Value) { + a := gg.NewValue("a") + aE0 := gg.NewValue("aE0") + aE1 := gg.NewValue("aE1") + aE2 := gg.NewValue("aE2") + aE3 := gg.NewValue("aE3") + b0 := gg.NewValue("b0") + b1 := gg.NewValue("b1") + b2 := gg.NewValue("b2") + b3 := gg.NewValue("b3") + oaE0 := gg.ValueOut(a, aE0) + oaE1 := gg.ValueOut(a, aE1) + oaE2 := gg.ValueOut(a, aE2) + oaE3 := gg.ValueOut(a, aE3) g := gg.Null - g = g.AddValueIn(aE0, gg.Str("b0")) - g = g.AddValueIn(aE1, gg.Str("b1")) - g = g.AddValueIn(aE2, gg.Str("b2")) - g = g.AddValueIn(aE3, gg.Str("b3")) + g = g.AddValueIn(oaE0, b0) + g = g.AddValueIn(oaE1, b1) + g = g.AddValueIn(oaE2, b2) + g = g.AddValueIn(oaE3, b3) + c := gg.NewValue("c") + empty := gg.NewValue("") jE := gg.JunctionOut([]gg.OpenEdge{ - gg.ValueOut(gg.Str("b0"), gg.Str("")), - gg.ValueOut(gg.Str("b1"), gg.Str("")), - gg.ValueOut(gg.Str("b2"), gg.Str("")), - gg.ValueOut(gg.Str("b3"), gg.Str("")), - }, gg.Str("jE")) - g = g.AddValueIn(jE, gg.Str("c")) - return g + gg.ValueOut(b0, empty), + gg.ValueOut(b1, empty), + gg.ValueOut(b2, empty), + gg.ValueOut(b3, empty), + }, gg.NewValue("jE")) + g = g.AddValueIn(jE, c) + return g, c } //func mkGraph() *gg.Graph { @@ -68,11 +79,12 @@ func main() { term.Reset() term.HideCursor() + g, start := mkGraph() v := view{ - g: mkGraph(), + g: g, primFlowDir: geo.Right, secFlowDir: geo.Down, - start: gg.Str("c"), + start: start, center: geo.Zero.Midpoint(term.WindowSize(), rounder), } diff --git a/gim/view.go b/gim/view.go index 0f9607a..12c8327 100644 --- a/gim/view.go +++ b/gim/view.go @@ -24,12 +24,12 @@ func posSolve(g *gg.Graph) [][]*gg.Vertex { secEng := constraint.NewEngine() strM := g.ByID() - for vID, v := range strM { + for _, v := range strM { var prevIn *gg.Vertex for _, e := range v.In { primEng.AddConstraint(constraint.Constraint{ Elem: e.From.ID, - LT: vID, + LT: v.ID, }) if prevIn != nil { secEng.AddConstraint(constraint.Constraint{ @@ -103,7 +103,7 @@ func centerBoxes(boxes []*box, around geo.XY) { type view struct { g *gg.Graph primFlowDir, secFlowDir geo.XY - start gg.Str + start gg.Value center geo.XY }