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
// Head.
type Edge struct {
Tail, Head Value
type Edge interface {
Tail() Value // The Value the Edge is coming from
Head() Value // The Value the Edge is going to
}
func (e Edge) id() string {
return fmt.Sprintf("%q->%q", e.Tail, e.Head)
func edgeID(e Edge) string {
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,
@ -125,7 +143,7 @@ func (g Graph) String() string {
// 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.
func (g Graph) Add(e Edge) Graph {
id := e.id()
id := edgeID(e)
if _, ok := g.m[id]; ok {
return g
}
@ -137,8 +155,8 @@ func (g Graph) Add(e Edge) Graph {
func (g Graph) addDirty(edgeID string, e Edge) {
g.m[edgeID] = e
g.vIns.add(e.Head.ID, edgeID)
g.vOuts.add(e.Tail.ID, edgeID)
g.vIns.add(e.Head().ID, edgeID)
g.vOuts.add(e.Tail().ID, edgeID)
}
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
// original Graph didn't have that Edge this returns the original Graph.
func (g Graph) Del(e Edge) Graph {
id := e.id()
id := edgeID(e)
if _, ok := g.m[id]; !ok {
return g
}
g2 := g.cp()
delete(g2.m, id)
g2.vIns.del(e.Head.ID, id)
g2.vOuts.del(e.Tail.ID, id)
g2.vIns.del(e.Head().ID, id)
g2.vOuts.del(e.Tail().ID, id)
return g2
}
@ -170,8 +188,8 @@ func (g Graph) Del(e Edge) Graph {
func (g Graph) Disjoin() []Graph {
valM := make(map[string]*Graph, len(g.vOuts))
graphForEdge := func(edge Edge) *Graph {
headGraph := valM[edge.Head.ID]
tailGraph := valM[edge.Tail.ID]
headGraph := valM[edge.Head().ID]
tailGraph := valM[edge.Tail().ID]
if headGraph == nil && tailGraph == nil {
newGraph := Graph{}.cp() // cp also initializes
return &newGraph
@ -203,8 +221,8 @@ func (g Graph) Disjoin() []Graph {
for edgeID, edge := range g.m {
graph := graphForEdge(edge)
(*graph).addDirty(edgeID, edge)
valM[edge.Head.ID] = graph
valM[edge.Tail.ID] = graph
valM[edge.Head().ID] = graph
valM[edge.Tail().ID] = graph
}
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
// where they are the same node
{
head := nodesM[edge.Head.ID]
head.Value = edge.Head
headV := edge.Head()
head := nodesM[headV.ID]
head.Value = headV
head.Ins = append(head.Ins, edge)
nodesM[head.ID] = head
}
{
tail := nodesM[edge.Tail.ID]
tail.Value = edge.Tail
tailV := edge.Tail()
tail := nodesM[tailV.ID]
tail.Value = tailV
tail.Outs = append(tail.Outs, edge)
nodesM[tail.ID] = tail
}
@ -359,10 +379,11 @@ func (g Graph) VisitBreadth(start Value, callback func(n Node) bool) {
}
visited[val.ID] = true
for _, edge := range node.Outs {
if visited[edge.Head.ID] {
headV := edge.Head()
if visited[headV.ID] {
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
for _, edge := range node.Outs {
if visited[edge.Head.ID] {
if visited[edge.Head().ID] {
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 {
s := ss.(state)
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
for _, e := range s.m {
p.add = e
@ -44,24 +44,24 @@ func TestGraph(t *T) {
}
} else if i == 1 {
// 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 {
// 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 {
// 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}
},
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
s, p := ss.(state), a.Params.(params)
if p.add != (Edge{}) {
if p.add != nil {
s.Graph = s.Graph.Add(p.add)
s.m[p.add.id()] = p.add
s.m[edgeID(p.add)] = p.add
} else {
s.Graph = s.Graph.Del(p.del)
delete(s.m, p.del.id())
delete(s.m, edgeID(p.del))
}
{ // test Nodes and Edges methods
@ -72,14 +72,14 @@ func TestGraph(t *T) {
ins, outs := map[string]int{}, map[string]int{}
for _, e := range s.m {
aa = append(aa, massert.Has(edges, e))
aa = append(aa, massert.HasKey(nodes, e.Head.ID))
aa = append(aa, massert.Has(nodes[e.Head.ID].Ins, e))
aa = append(aa, massert.HasKey(nodes, e.Tail.ID))
aa = append(aa, massert.Has(nodes[e.Tail.ID].Outs, e))
vals[e.Head.ID] = true
vals[e.Tail.ID] = true
ins[e.Head.ID]++
outs[e.Tail.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.HasKey(nodes, e.Tail().ID))
aa = append(aa, massert.Has(nodes[e.Tail().ID].Outs, e))
vals[e.Head().ID] = true
vals[e.Tail().ID] = true
ins[e.Head().ID]++
outs[e.Tail().ID]++
}
aa = append(aa, massert.Len(edges, len(s.m)))
aa = append(aa, massert.Len(nodes, len(vals)))
@ -145,7 +145,7 @@ func TestSubGraphAndEqual(t *T) {
Next: func(ss mchk.State) mchk.Action {
i := mrand.Intn(10)
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,
add2: i != 1,
}
@ -206,20 +206,20 @@ func TestDisjoinUnion(t *T) {
prefix := mrand.Hex(1)
var edge Edge
if vals := s.valM[prefix]; len(vals) == 0 {
edge = Edge{
Tail: strV(prefix + mrand.Hex(1)),
Head: strV(prefix + mrand.Hex(1)),
}
edge = NewEdge(
strV(prefix+mrand.Hex(1)),
strV(prefix+mrand.Hex(1)),
)
} else if mrand.Intn(2) == 0 {
edge = Edge{
Tail: mrand.Element(vals, nil).(Value),
Head: strV(prefix + mrand.Hex(1)),
}
edge = NewEdge(
mrand.Element(vals, nil).(Value),
strV(prefix+mrand.Hex(1)),
)
} else {
edge = Edge{
Tail: strV(prefix + mrand.Hex(1)),
Head: mrand.Element(vals, nil).(Value),
}
edge = NewEdge(
strV(prefix+mrand.Hex(1)),
mrand.Element(vals, nil).(Value),
)
}
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) {
s, p := ss.(state), a.Params.(params)
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)
var aa []massert.Assertion
@ -314,11 +314,15 @@ func TestVisitBreadth(t *T) {
var p params
p.newRank = len(thisRank(s)) > 0 && mrand.Intn(10) == 0
if p.newRank {
p.e.Head = randNew(s)
p.e.Tail = randFromRank(s, thisRank)
p.e = NewEdge(
randFromRank(s, thisRank),
randNew(s),
)
} else {
p.e.Head = strV(mrand.Hex(2))
p.e.Tail = randFromRank(s, prevRank)
p.e = NewEdge(
randFromRank(s, prevRank),
strV(mrand.Hex(2)),
)
}
return mchk.Action{Params: p}
},
@ -327,8 +331,8 @@ func TestVisitBreadth(t *T) {
if p.newRank {
s.ranks = append(s.ranks, map[string]bool{})
}
if !s.g.Has(p.e.Head) {
thisRank(s)[p.e.Head.ID] = true
if !s.g.Has(p.e.Head()) {
thisRank(s)[p.e.Head().ID] = true
}
s.g = s.g.Add(p.e)