diff --git a/lang/gg/gg.go b/lang/gg/gg.go index 7cc4edd..0a6ccd8 100644 --- a/lang/gg/gg.go +++ b/lang/gg/gg.go @@ -8,6 +8,8 @@ import ( "hash" ) +// TODO rename half-edge to open-edge + // Identifier is implemented by any value which can return a unique string for // itself via an Identify method type Identifier interface { @@ -121,6 +123,18 @@ func (v vertex) hasHalfEdge(he HalfEdge) bool { return false } +func (v vertex) cpAndDelHalfEdge(he HalfEdge) (vertex, bool) { + heID := identify(he) + for i, in := range v.in { + if identify(in) == heID { + 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 @@ -182,11 +196,11 @@ func JunctionOut(in []HalfEdge, edgeVal Identifier) HalfEdge { } } -// ValueIn takes a HalfEdge and connects it to the Value Vertex containing val, -// and returns the new Graph which reflects that connection. Any Vertices +// AddValueIn takes a HalfEdge and connects it to the Value Vertex containing +// val, returning the new Graph which reflects that connection. Any Vertices // referenced within the HalfEdge which do not yet exist in the Graph will also // be created in this step. -func (g *Graph) ValueIn(he HalfEdge, val Identifier) *Graph { +func (g *Graph) AddValueIn(he HalfEdge, val Identifier) *Graph { to := vertex{ VertexType: Value, val: val, @@ -217,18 +231,121 @@ func (g *Graph) ValueIn(he HalfEdge, val Identifier) *Graph { if _, ok := g.vM[vID]; !ok { g.vM[vID] = v } - } - for _, e := range v.in { - persist(e.fromV) + } else { + for _, e := range v.in { + persist(e.fromV) + } } } delete(g.vM, toID) persist(to) + for _, e := range to.in { + persist(e.fromV) + } return g } -// TODO Merge +// DelValueIn takes a HalfEdge 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 +// HalfEdge, no changes are made. Any vertices referenced by the HalfEdge for +// which that edge is their only outgoing edge will be removed from the Graph. +func (g *Graph) DelValueIn(he HalfEdge, val Identifier) *Graph { + to := vertex{ + VertexType: Value, + val: val, + } + toID := identify(to) + + // 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.cpAndDelHalfEdge(he) + 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 == Value && identify(in.fromV) == vID { + return true + } else if in.fromV.VertexType == Junction && 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 := identify(v) + 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 HalfEdge and removes any Value + // Vertices referenced in it which are now orphaned + var rmOrphaned func(HalfEdge) + rmOrphaned = func(he HalfEdge) { + if he.fromV.VertexType == Value && isOrphaned(he.fromV) { + delete(g.vM, identify(he.fromV)) + } else if he.fromV.VertexType == Junction { + for _, juncHe := range he.fromV.in { + rmOrphaned(juncHe) + } + } + } + rmOrphaned(he) + + return g +} + +// Union takes in another Graph and returns a new one which is the union of the +// two. Value vertices which are shared between the two will be merged so that +// the new vertex has the input edges of both. +func (g *Graph) Union(g2 *Graph) *Graph { + g = g.cp() + for vID, v2 := range g2.vM { + v, ok := g.vM[vID] + if !ok { + v = v2 + } else { + for _, v2e := range v2.in { + if !v.hasHalfEdge(v2e) { + v.in = append(v.in, v2e) + } + } + } + g.vM[vID] = v + } + return g +} //////////////////////////////////////////////////////////////////////////////// // Graph traversal @@ -287,6 +404,16 @@ func (g *Graph) Value(val Identifier) *Vertex { return g.view[identify(val)] } +// Values returns all Value Vertices in the Graph +func (g *Graph) Values() []*Vertex { + g.makeView() + vv := make([]*Vertex, 0, len(g.view)) + for _, v := range g.view { + 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) { diff --git a/lang/gg/gg_test.go b/lang/gg/gg_test.go index 668e11b..9f8a51d 100644 --- a/lang/gg/gg_test.go +++ b/lang/gg/gg_test.go @@ -83,7 +83,7 @@ func TestGraph(t *T) { mkTest( "values-basic", func() *Graph { - return Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + return Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) }, value("v0"), value("v1", edge("e0", value("v0"))), @@ -92,8 +92,8 @@ func TestGraph(t *T) { mkTest( "values-2edges", func() *Graph { - g0 := Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v2")) - return g0.ValueIn(ValueOut(id("v1"), id("e1")), id("v2")) + g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) + return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) }, value("v0"), value("v1"), @@ -106,8 +106,8 @@ func TestGraph(t *T) { mkTest( "values-separate", func() *Graph { - g0 := Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - return g0.ValueIn(ValueOut(id("v2"), id("e2")), id("v3")) + g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + return g0.AddValueIn(ValueOut(id("v2"), id("e2")), id("v3")) }, value("v0"), value("v1", edge("e0", value("v0"))), @@ -118,7 +118,7 @@ func TestGraph(t *T) { mkTest( "values-circular", func() *Graph { - return Null.ValueIn(ValueOut(id("v0"), id("e")), id("v0")) + return Null.AddValueIn(ValueOut(id("v0"), id("e")), id("v0")) }, value("v0", edge("e", value("v0"))), ), @@ -126,8 +126,8 @@ func TestGraph(t *T) { mkTest( "values-circular2", func() *Graph { - g0 := Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - return g0.ValueIn(ValueOut(id("v1"), id("e1")), id("v0")) + g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v0")) }, value("v0", edge("e1", value("v1", edge("e0", value("v0"))))), value("v1", edge("e0", value("v0", edge("e1", value("v1"))))), @@ -136,9 +136,9 @@ func TestGraph(t *T) { mkTest( "values-circular3", func() *Graph { - g0 := Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - g1 := g0.ValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - return g1.ValueIn(ValueOut(id("v2"), id("e2")), id("v1")) + 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")) }, value("v0"), value("v1", @@ -157,7 +157,7 @@ func TestGraph(t *T) { e0 := ValueOut(id("v0"), id("e0")) e1 := ValueOut(id("v1"), id("e1")) ej0 := JunctionOut([]HalfEdge{e0, e1}, id("ej0")) - return Null.ValueIn(ej0, id("v2")) + return Null.AddValueIn(ej0, id("v2")) }, value("v0"), value("v1"), value("v2", junction("ej0", @@ -176,7 +176,7 @@ func TestGraph(t *T) { e11 := ValueOut(id("v1"), id("e11")) ej1 := JunctionOut([]HalfEdge{e01, e11}, id("ej1")) ej2 := JunctionOut([]HalfEdge{ej0, ej1}, id("ej2")) - return Null.ValueIn(ej2, id("v2")) + return Null.AddValueIn(ej2, id("v2")) }, value("v0"), value("v1"), value("v2", junction("ej2", @@ -197,11 +197,11 @@ func TestGraph(t *T) { e0 := ValueOut(id("v0"), id("e0")) e1 := ValueOut(id("v1"), id("e1")) ej0 := JunctionOut([]HalfEdge{e0, e1}, id("ej0")) - g0 := Null.ValueIn(ej0, id("v2")) + g0 := Null.AddValueIn(ej0, id("v2")) e20 := ValueOut(id("v2"), id("e20")) - g1 := g0.ValueIn(e20, id("v0")) + g1 := g0.AddValueIn(e20, id("v0")) e21 := ValueOut(id("v2"), id("e21")) - return g1.ValueIn(e21, id("v1")) + return g1.AddValueIn(e21, id("v1")) }, value("v0", edge("e20", value("v2", junction("ej0", edge("e0", value("v0")), @@ -239,7 +239,7 @@ func TestGraph(t *T) { func TestGraphImmutability(t *T) { e0 := ValueOut(id("v0"), id("e0")) - g0 := Null.ValueIn(e0, id("v1")) + 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"))) @@ -247,21 +247,219 @@ func TestGraphImmutability(t *T) { // half-edges should be re-usable e1 := ValueOut(id("v2"), id("e1")) - g1a := g0.ValueIn(e1, id("v3a")) - g1b := g0.ValueIn(e1, id("v3b")) + 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"))) // ... even re-usable twice in succession - g2 := g0.ValueIn(e1, id("v3")).ValueIn(e1, id("v4")) + 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"))) } +func TestGraphDelValueIn(t *T) { + { // removing from null + g := Null.DelValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + assert.True(t, Equal(Null, g)) + } + + { // 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")) + assert.True(t, Equal(g0, g1)) + } + + { // removing only edge + he := ValueOut(id("v0"), id("e0")) + g0 := Null.AddValueIn(he, id("v1")) + g1 := g0.DelValueIn(he, id("v1")) + assert.True(t, Equal(Null, g1)) + } + + { // removing only edge (junction) + he := JunctionOut([]HalfEdge{ + ValueOut(id("v0"), id("e0")), + ValueOut(id("v1"), id("e1")), + }, id("ej0")) + g0 := Null.AddValueIn(he, id("v2")) + g1 := g0.DelValueIn(he, id("v2")) + assert.True(t, Equal(Null, g1)) + } + + { // removing one of two edges + he := ValueOut(id("v1"), id("e0")) + g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) + g1 := g0.AddValueIn(he, id("v2")) + g2 := g1.DelValueIn(he, id("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"))) + } + + { // removing one of two edges (junction) + e0 := ValueOut(id("v0"), id("e0")) + e1 := ValueOut(id("v1"), id("e1")) + e2 := ValueOut(id("v2"), id("e2")) + heA := JunctionOut([]HalfEdge{e0, e1}, id("heA")) + heB := JunctionOut([]HalfEdge{e1, e2}, id("heB")) + g0a := Null.AddValueIn(heA, id("v3")) + g0b := Null.AddValueIn(heB, id("v3")) + g1 := g0a.Union(g0b).DelValueIn(heA, id("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"))) + } + + { // 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"))) + } + + { // removing to's only edge, sub-nodes have edge to each other + ej := JunctionOut([]HalfEdge{ + 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"))) + } +} + +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 + } + + { // Union with Null + assert.True(t, Equal(Null, Null.Union(Null))) + + g := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + assert.True(t, Equal(g, assertUnion(g, Null))) + } + + { // 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")) + 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"))) + } + + { // Two disparate graphs with junctions + ga := Null.AddValueIn(JunctionOut([]HalfEdge{ + ValueOut(id("va0"), id("ea0")), + ValueOut(id("va1"), id("ea1")), + }, id("eaj")), id("va2")) + gb := Null.AddValueIn(JunctionOut([]HalfEdge{ + ValueOut(id("vb0"), id("eb0")), + ValueOut(id("vb1"), id("eb1")), + }, id("ebj")), id("vb2")) + g := assertUnion(ga, gb) + assertVertexEqual(t, value("va0"), g.Value(id("va0"))) + assertVertexEqual(t, value("va1"), g.Value(id("va1"))) + assertVertexEqual(t, + value("va2", junction("eaj", + edge("ea0", value("va0")), + edge("ea1", value("va1")))), + g.Value(id("va2")), + ) + assertVertexEqual(t, value("vb0"), g.Value(id("vb0"))) + assertVertexEqual(t, value("vb1"), g.Value(id("vb1"))) + assertVertexEqual(t, + value("vb2", junction("ebj", + edge("eb0", value("vb0")), + edge("eb1", value("vb1")))), + g.Value(id("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")) + g := assertUnion(g0, g1) + assertVertexEqual(t, value("v0"), g.Value(id("v0"))) + assertVertexEqual(t, value("v1"), g.Value(id("v1"))) + assertVertexEqual(t, + value("v2", + edge("e0", value("v0")), + edge("e1", value("v1")), + ), + g.Value(id("v2")), + ) + } + + { // two partially overlapping graphs with junctions + g0 := Null.AddValueIn(JunctionOut([]HalfEdge{ + ValueOut(id("v0"), id("e0")), + ValueOut(id("v1"), id("e1")), + }, id("ej0")), id("v2")) + g1 := Null.AddValueIn(JunctionOut([]HalfEdge{ + ValueOut(id("v0"), id("e0")), + ValueOut(id("v1"), id("e1")), + }, id("ej1")), id("v2")) + g := assertUnion(g0, g1) + assertVertexEqual(t, value("v0"), g.Value(id("v0"))) + assertVertexEqual(t, value("v1"), g.Value(id("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"))), + ), + g.Value(id("v2")), + ) + } + + { // Two equal graphs + g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + g := assertUnion(g0, g0) + assertVertexEqual(t, value("v0"), g.Value(id("v0"))) + assertVertexEqual(t, + value("v1", edge("e0", value("v0"))), + g.Value(id("v1")), + ) + } + + { // Two equal graphs with junctions + g0 := Null.AddValueIn(JunctionOut([]HalfEdge{ + ValueOut(id("v0"), id("e0")), + ValueOut(id("v1"), id("e1")), + }, id("ej0")), id("v2")) + g := assertUnion(g0, g0) + assertVertexEqual(t, value("v0"), g.Value(id("v0"))) + assertVertexEqual(t, value("v1"), g.Value(id("v1"))) + assertVertexEqual(t, + value("v2", + junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))), + ), + g.Value(id("v2")), + ) + } +} + func TestGraphEqual(t *T) { assertEqual := func(g1, g2 *Graph) { assert.True(t, Equal(g1, g2)) @@ -278,32 +476,32 @@ func TestGraphEqual(t *T) { { // graph is equal to itself, not to null e0 := ValueOut(id("v0"), id("e0")) - g0 := Null.ValueIn(e0, id("v1")) + g0 := Null.AddValueIn(e0, id("v1")) assertNotEqual(g0, Null) assertEqual(g0, g0) // adding the an existing edge again shouldn't do anything - assertEqual(g0, g0.ValueIn(e0, id("v1"))) + assertEqual(g0, g0.AddValueIn(e0, id("v1"))) // g1a and g1b have the same vertices, but the edges are different - g1a := g0.ValueIn(ValueOut(id("v0"), id("e1a")), id("v2")) - g1b := g0.ValueIn(ValueOut(id("v0"), id("e1b")), id("v2")) + g1a := g0.AddValueIn(ValueOut(id("v0"), id("e1a")), id("v2")) + g1b := g0.AddValueIn(ValueOut(id("v0"), id("e1b")), id("v2")) assertNotEqual(g1a, g1b) } { // equal construction should yield equality, even if out of order - ga := Null.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) - ga = ga.ValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - gb := Null.ValueIn(ValueOut(id("v1"), id("e1")), id("v2")) - gb = gb.ValueIn(ValueOut(id("v0"), id("e0")), id("v1")) + 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")) assertEqual(ga, gb) } { // junction basic test e0 := ValueOut(id("v0"), id("e0")) e1 := ValueOut(id("v1"), id("e1")) - ga := Null.ValueIn(JunctionOut([]HalfEdge{e0, e1}, id("ej")), id("v2")) - gb := Null.ValueIn(JunctionOut([]HalfEdge{e1, e0}, id("ej")), id("v2")) + ga := Null.AddValueIn(JunctionOut([]HalfEdge{e0, e1}, id("ej")), id("v2")) + gb := Null.AddValueIn(JunctionOut([]HalfEdge{e1, e0}, id("ej")), id("v2")) assertEqual(ga, ga) assertNotEqual(ga, gb) }