diff --git a/graph/graph.go b/graph/graph.go index b38e4a0..4b0ca0b 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -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()) } } } diff --git a/graph/graph_test.go b/graph/graph_test.go index 578fc80..fa63d5d 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -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)