graph: make Edge into an interface

This commit is contained in:
Brian Picciano 2018-10-04 15:24:17 -04:00
parent a5b3b7acd0
commit 16f2b1bbde
2 changed files with 82 additions and 57 deletions

View File

@ -37,12 +37,30 @@ func NewValue(V interface{}) Value {
// Edge is a directional edge connecting two values in a Graph, the Tail and the // Edge is a directional edge connecting two values in a Graph, the Tail and the
// Head. // Head.
type Edge struct { type Edge interface {
Tail, Head Value Tail() Value // The Value the Edge is coming from
Head() Value // The Value the Edge is going to
} }
func (e Edge) id() string { func edgeID(e Edge) string {
return fmt.Sprintf("%q->%q", e.Tail, e.Head) return fmt.Sprintf("%q->%q", e.Tail().ID, e.Head().ID)
}
type edge struct {
tail, head Value
}
// NewEdge constructs and returns an Edge running from tail to head.
func NewEdge(tail, head Value) Edge {
return edge{tail, head}
}
func (e edge) Tail() Value {
return e.tail
}
func (e edge) Head() Value {
return e.head
} }
// an edgeIndex maps valueIDs to a set of edgeIDs. Graph keeps two edgeIndex's, // an edgeIndex maps valueIDs to a set of edgeIDs. Graph keeps two edgeIndex's,
@ -125,7 +143,7 @@ func (g Graph) String() string {
// Add returns a new Graph instance with the given Edge added to it. If the // Add returns a new Graph instance with the given Edge added to it. If the
// original Graph already had that Edge this returns the original Graph. // original Graph already had that Edge this returns the original Graph.
func (g Graph) Add(e Edge) Graph { func (g Graph) Add(e Edge) Graph {
id := e.id() id := edgeID(e)
if _, ok := g.m[id]; ok { if _, ok := g.m[id]; ok {
return g return g
} }
@ -137,8 +155,8 @@ func (g Graph) Add(e Edge) Graph {
func (g Graph) addDirty(edgeID string, e Edge) { func (g Graph) addDirty(edgeID string, e Edge) {
g.m[edgeID] = e g.m[edgeID] = e
g.vIns.add(e.Head.ID, edgeID) g.vIns.add(e.Head().ID, edgeID)
g.vOuts.add(e.Tail.ID, edgeID) g.vOuts.add(e.Tail().ID, edgeID)
} }
func (g Graph) estSize() int { func (g Graph) estSize() int {
@ -153,15 +171,15 @@ func (g Graph) estSize() int {
// Del returns a new Graph instance without the given Edge in it. If the // Del returns a new Graph instance without the given Edge in it. If the
// original Graph didn't have that Edge this returns the original Graph. // original Graph didn't have that Edge this returns the original Graph.
func (g Graph) Del(e Edge) Graph { func (g Graph) Del(e Edge) Graph {
id := e.id() id := edgeID(e)
if _, ok := g.m[id]; !ok { if _, ok := g.m[id]; !ok {
return g return g
} }
g2 := g.cp() g2 := g.cp()
delete(g2.m, id) delete(g2.m, id)
g2.vIns.del(e.Head.ID, id) g2.vIns.del(e.Head().ID, id)
g2.vOuts.del(e.Tail.ID, id) g2.vOuts.del(e.Tail().ID, id)
return g2 return g2
} }
@ -170,8 +188,8 @@ func (g Graph) Del(e Edge) Graph {
func (g Graph) Disjoin() []Graph { func (g Graph) Disjoin() []Graph {
valM := make(map[string]*Graph, len(g.vOuts)) valM := make(map[string]*Graph, len(g.vOuts))
graphForEdge := func(edge Edge) *Graph { graphForEdge := func(edge Edge) *Graph {
headGraph := valM[edge.Head.ID] headGraph := valM[edge.Head().ID]
tailGraph := valM[edge.Tail.ID] tailGraph := valM[edge.Tail().ID]
if headGraph == nil && tailGraph == nil { if headGraph == nil && tailGraph == nil {
newGraph := Graph{}.cp() // cp also initializes newGraph := Graph{}.cp() // cp also initializes
return &newGraph return &newGraph
@ -203,8 +221,8 @@ func (g Graph) Disjoin() []Graph {
for edgeID, edge := range g.m { for edgeID, edge := range g.m {
graph := graphForEdge(edge) graph := graphForEdge(edge)
(*graph).addDirty(edgeID, edge) (*graph).addDirty(edgeID, edge)
valM[edge.Head.ID] = graph valM[edge.Head().ID] = graph
valM[edge.Tail.ID] = graph valM[edge.Tail().ID] = graph
} }
found := map[*Graph]bool{} found := map[*Graph]bool{}
@ -276,14 +294,16 @@ func (g Graph) Nodes() map[string]Node {
// if head and tail are modified at the same time it messes up the case // if head and tail are modified at the same time it messes up the case
// where they are the same node // where they are the same node
{ {
head := nodesM[edge.Head.ID] headV := edge.Head()
head.Value = edge.Head head := nodesM[headV.ID]
head.Value = headV
head.Ins = append(head.Ins, edge) head.Ins = append(head.Ins, edge)
nodesM[head.ID] = head nodesM[head.ID] = head
} }
{ {
tail := nodesM[edge.Tail.ID] tailV := edge.Tail()
tail.Value = edge.Tail tail := nodesM[tailV.ID]
tail.Value = tailV
tail.Outs = append(tail.Outs, edge) tail.Outs = append(tail.Outs, edge)
nodesM[tail.ID] = tail nodesM[tail.ID] = tail
} }
@ -359,10 +379,11 @@ func (g Graph) VisitBreadth(start Value, callback func(n Node) bool) {
} }
visited[val.ID] = true visited[val.ID] = true
for _, edge := range node.Outs { for _, edge := range node.Outs {
if visited[edge.Head.ID] { headV := edge.Head()
if visited[headV.ID] {
continue continue
} }
toVisit = append(toVisit, edge.Head) toVisit = append(toVisit, headV)
} }
} }
} }
@ -400,10 +421,10 @@ func (g Graph) VisitDepth(start Value, callback func(n Node) bool) {
} }
visited[val.ID] = true visited[val.ID] = true
for _, edge := range node.Outs { for _, edge := range node.Outs {
if visited[edge.Head.ID] { if visited[edge.Head().ID] {
continue continue
} }
toVisit = append(toVisit, edge.Head) toVisit = append(toVisit, edge.Head())
} }
} }
} }

View File

@ -36,7 +36,7 @@ func TestGraph(t *T) {
Next: func(ss mchk.State) mchk.Action { Next: func(ss mchk.State) mchk.Action {
s := ss.(state) s := ss.(state)
var p params var p params
if i := mrand.Intn(10); i == 0 { if i := mrand.Intn(10); i == 0 && len(s.m) > 0 {
// add edge which is already there // add edge which is already there
for _, e := range s.m { for _, e := range s.m {
p.add = e p.add = e
@ -44,24 +44,24 @@ func TestGraph(t *T) {
} }
} else if i == 1 { } else if i == 1 {
// delete edge which isn't there // delete edge which isn't there
p.del = Edge{Tail: strV("z"), Head: strV("z")} p.del = NewEdge(strV("z"), strV("z"))
} else if i <= 5 { } else if i <= 5 {
// add probably new edge // add probably new edge
p.add = Edge{Tail: strV(mrand.Hex(1)), Head: strV(mrand.Hex(1))} p.add = NewEdge(strV(mrand.Hex(1)), strV(mrand.Hex(1)))
} else { } else {
// probably del edge // probably del edge
p.del = Edge{Tail: strV(mrand.Hex(1)), Head: strV(mrand.Hex(1))} p.del = NewEdge(strV(mrand.Hex(1)), strV(mrand.Hex(1)))
} }
return mchk.Action{Params: p} return mchk.Action{Params: p}
}, },
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
s, p := ss.(state), a.Params.(params) s, p := ss.(state), a.Params.(params)
if p.add != (Edge{}) { if p.add != nil {
s.Graph = s.Graph.Add(p.add) s.Graph = s.Graph.Add(p.add)
s.m[p.add.id()] = p.add s.m[edgeID(p.add)] = p.add
} else { } else {
s.Graph = s.Graph.Del(p.del) s.Graph = s.Graph.Del(p.del)
delete(s.m, p.del.id()) delete(s.m, edgeID(p.del))
} }
{ // test Nodes and Edges methods { // test Nodes and Edges methods
@ -72,14 +72,14 @@ func TestGraph(t *T) {
ins, outs := map[string]int{}, map[string]int{} ins, outs := map[string]int{}, map[string]int{}
for _, e := range s.m { for _, e := range s.m {
aa = append(aa, massert.Has(edges, e)) aa = append(aa, massert.Has(edges, e))
aa = append(aa, massert.HasKey(nodes, e.Head.ID)) aa = append(aa, massert.HasKey(nodes, e.Head().ID))
aa = append(aa, massert.Has(nodes[e.Head.ID].Ins, e)) aa = append(aa, massert.Has(nodes[e.Head().ID].Ins, e))
aa = append(aa, massert.HasKey(nodes, e.Tail.ID)) aa = append(aa, massert.HasKey(nodes, e.Tail().ID))
aa = append(aa, massert.Has(nodes[e.Tail.ID].Outs, e)) aa = append(aa, massert.Has(nodes[e.Tail().ID].Outs, e))
vals[e.Head.ID] = true vals[e.Head().ID] = true
vals[e.Tail.ID] = true vals[e.Tail().ID] = true
ins[e.Head.ID]++ ins[e.Head().ID]++
outs[e.Tail.ID]++ outs[e.Tail().ID]++
} }
aa = append(aa, massert.Len(edges, len(s.m))) aa = append(aa, massert.Len(edges, len(s.m)))
aa = append(aa, massert.Len(nodes, len(vals))) aa = append(aa, massert.Len(nodes, len(vals)))
@ -145,7 +145,7 @@ func TestSubGraphAndEqual(t *T) {
Next: func(ss mchk.State) mchk.Action { Next: func(ss mchk.State) mchk.Action {
i := mrand.Intn(10) i := mrand.Intn(10)
p := params{ p := params{
e: Edge{Tail: strV(mrand.Hex(4)), Head: strV(mrand.Hex(4))}, e: NewEdge(strV(mrand.Hex(4)), strV(mrand.Hex(4))),
add1: i != 0, add1: i != 0,
add2: i != 1, add2: i != 1,
} }
@ -206,20 +206,20 @@ func TestDisjoinUnion(t *T) {
prefix := mrand.Hex(1) prefix := mrand.Hex(1)
var edge Edge var edge Edge
if vals := s.valM[prefix]; len(vals) == 0 { if vals := s.valM[prefix]; len(vals) == 0 {
edge = Edge{ edge = NewEdge(
Tail: strV(prefix + mrand.Hex(1)), strV(prefix+mrand.Hex(1)),
Head: strV(prefix + mrand.Hex(1)), strV(prefix+mrand.Hex(1)),
} )
} else if mrand.Intn(2) == 0 { } else if mrand.Intn(2) == 0 {
edge = Edge{ edge = NewEdge(
Tail: mrand.Element(vals, nil).(Value), mrand.Element(vals, nil).(Value),
Head: strV(prefix + mrand.Hex(1)), strV(prefix+mrand.Hex(1)),
} )
} else { } else {
edge = Edge{ edge = NewEdge(
Tail: strV(prefix + mrand.Hex(1)), strV(prefix+mrand.Hex(1)),
Head: mrand.Element(vals, nil).(Value), mrand.Element(vals, nil).(Value),
} )
} }
return mchk.Action{Params: params{prefix: prefix, e: edge}} return mchk.Action{Params: params{prefix: prefix, e: edge}}
@ -227,7 +227,7 @@ func TestDisjoinUnion(t *T) {
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
s, p := ss.(state), a.Params.(params) s, p := ss.(state), a.Params.(params)
s.g = s.g.Add(p.e) s.g = s.g.Add(p.e)
s.valM[p.prefix] = append(s.valM[p.prefix], p.e.Head, p.e.Tail) s.valM[p.prefix] = append(s.valM[p.prefix], p.e.Head(), p.e.Tail())
s.disjM[p.prefix] = s.disjM[p.prefix].Add(p.e) s.disjM[p.prefix] = s.disjM[p.prefix].Add(p.e)
var aa []massert.Assertion var aa []massert.Assertion
@ -314,11 +314,15 @@ func TestVisitBreadth(t *T) {
var p params var p params
p.newRank = len(thisRank(s)) > 0 && mrand.Intn(10) == 0 p.newRank = len(thisRank(s)) > 0 && mrand.Intn(10) == 0
if p.newRank { if p.newRank {
p.e.Head = randNew(s) p.e = NewEdge(
p.e.Tail = randFromRank(s, thisRank) randFromRank(s, thisRank),
randNew(s),
)
} else { } else {
p.e.Head = strV(mrand.Hex(2)) p.e = NewEdge(
p.e.Tail = randFromRank(s, prevRank) randFromRank(s, prevRank),
strV(mrand.Hex(2)),
)
} }
return mchk.Action{Params: p} return mchk.Action{Params: p}
}, },
@ -327,8 +331,8 @@ func TestVisitBreadth(t *T) {
if p.newRank { if p.newRank {
s.ranks = append(s.ranks, map[string]bool{}) s.ranks = append(s.ranks, map[string]bool{})
} }
if !s.g.Has(p.e.Head) { if !s.g.Has(p.e.Head()) {
thisRank(s)[p.e.Head.ID] = true thisRank(s)[p.e.Head().ID] = true
} }
s.g = s.g.Add(p.e) s.g = s.g.Add(p.e)