graph: refactor to use Node type
This commit is contained in:
parent
9534ff5c13
commit
20b2a80a3c
114
graph/graph.go
114
graph/graph.go
@ -111,9 +111,9 @@ func (g Graph) cp() Graph {
|
||||
return g2
|
||||
}
|
||||
|
||||
// AddEdge 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.
|
||||
func (g Graph) AddEdge(e Edge) Graph {
|
||||
func (g Graph) Add(e Edge) Graph {
|
||||
id := e.id()
|
||||
if _, ok := g.m[id]; ok {
|
||||
return g
|
||||
@ -126,9 +126,9 @@ func (g Graph) AddEdge(e Edge) Graph {
|
||||
return g2
|
||||
}
|
||||
|
||||
// DelEdge 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.
|
||||
func (g Graph) DelEdge(e Edge) Graph {
|
||||
func (g Graph) Del(e Edge) Graph {
|
||||
id := e.id()
|
||||
if _, ok := g.m[id]; !ok {
|
||||
return g
|
||||
@ -141,24 +141,6 @@ func (g Graph) DelEdge(e Edge) Graph {
|
||||
return g2
|
||||
}
|
||||
|
||||
// Values returns all Values which have incoming or outgoing Edges in the Graph.
|
||||
func (g Graph) Values() []Value {
|
||||
values := make([]Value, 0, len(g.m))
|
||||
found := map[string]bool{}
|
||||
tryAdd := func(v Value) {
|
||||
if ok := found[v.ID]; !ok {
|
||||
values = append(values, v)
|
||||
found[v.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range g.m {
|
||||
tryAdd(e.Head)
|
||||
tryAdd(e.Tail)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Edges returns all Edges which are part of the Graph
|
||||
func (g Graph) Edges() []Edge {
|
||||
edges := make([]Edge, 0, len(g.m))
|
||||
@ -168,38 +150,88 @@ func (g Graph) Edges() []Edge {
|
||||
return edges
|
||||
}
|
||||
|
||||
// ValueEdges returns all input (e.Head==v) and output (e.Tail==v) Edges
|
||||
// for the given Value in the Graph.
|
||||
func (g Graph) ValueEdges(v Value) ([]Edge, []Edge) {
|
||||
in := make([]Edge, 0, len(g.vIns[v.ID]))
|
||||
for edgeID := range g.vIns[v.ID] {
|
||||
in = append(in, g.m[edgeID])
|
||||
// NOTE the Node type exists primarily for convenience. As far as Graph's
|
||||
// internals are concerned it doesn't _really_ exist, and no Graph method should
|
||||
// ever take Node as a parameter (except the callback functions like in
|
||||
// Traverse, where it's not really being taken in).
|
||||
|
||||
// Node wraps a Value in a Graph to include that Node's input and output Edges
|
||||
// in that Graph.
|
||||
type Node struct {
|
||||
Value
|
||||
|
||||
// All Edges in the Graph with this Node's Value as their Head and Tail,
|
||||
// respectively.
|
||||
Ins, Outs []Edge
|
||||
}
|
||||
|
||||
out := make([]Edge, 0, len(g.vOuts[v.ID]))
|
||||
for edgeID := range g.vOuts[v.ID] {
|
||||
out = append(out, g.m[edgeID])
|
||||
// Node returns the Node for the given Value, or false if the Graph doesn't
|
||||
// contain the Value.
|
||||
func (g Graph) Node(v Value) (Node, bool) {
|
||||
n := Node{Value: v}
|
||||
for edgeID := range g.vIns[v.ID] {
|
||||
n.Ins = append(n.Ins, g.m[edgeID])
|
||||
}
|
||||
return in, out
|
||||
for edgeID := range g.vOuts[v.ID] {
|
||||
n.Outs = append(n.Outs, g.m[edgeID])
|
||||
}
|
||||
return n, len(n.Ins) > 0 || len(n.Outs) > 0
|
||||
}
|
||||
|
||||
// Nodes returns a Node for each Value which has at least one Edge in the Graph,
|
||||
// with the Nodes mapped by their Value's ID.
|
||||
func (g Graph) Nodes() map[string]Node {
|
||||
nodesM := make(map[string]Node, len(g.m)*2)
|
||||
for _, edge := range g.m {
|
||||
// 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
|
||||
head.Ins = append(head.Ins, edge)
|
||||
nodesM[head.ID] = head
|
||||
}
|
||||
{
|
||||
tail := nodesM[edge.Tail.ID]
|
||||
tail.Value = edge.Tail
|
||||
tail.Outs = append(tail.Outs, edge)
|
||||
nodesM[tail.ID] = tail
|
||||
}
|
||||
}
|
||||
return nodesM
|
||||
}
|
||||
|
||||
// Has returns true if the Graph contains at least one Edge with a Head or Tail
|
||||
// of Value.
|
||||
func (g Graph) Has(v Value) bool {
|
||||
if _, ok := g.vIns[v.ID]; ok {
|
||||
return true
|
||||
} else if _, ok := g.vOuts[v.ID]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Traverse is used to traverse the Graph until a stopping point is reached.
|
||||
// Traversal starts with the cursor at the given start value. Each hop is
|
||||
// performed by passing the cursor value along with its input and output Edges
|
||||
// into the next function. The cursor moves to the returned Value and next is
|
||||
// called again, and so on.
|
||||
// Traversal starts with the cursor at the given start Value. Each hop is
|
||||
// performed by passing the cursor Value's Node into the next function. The
|
||||
// cursor moves to the returned Value and next is called again, and so on.
|
||||
//
|
||||
// If the boolean returned from the next function is false traversal stops and
|
||||
// this method returns.
|
||||
//
|
||||
// If start has no Edges in the Graph, or a Value returned from next doesn't,
|
||||
// this will still call next, but the in/out params will both be empty.
|
||||
func (g Graph) Traverse(start Value, next func(v Value, in, out []Edge) (Value, bool)) {
|
||||
// this will still call next, but the Node will be the zero value.
|
||||
func (g Graph) Traverse(start Value, next func(n Node) (Value, bool)) {
|
||||
curr := start
|
||||
var ok bool
|
||||
for {
|
||||
in, out := g.ValueEdges(curr)
|
||||
if curr, ok = next(curr, in, out); !ok {
|
||||
currNode, ok := g.Node(curr)
|
||||
if ok {
|
||||
curr, ok = next(currNode)
|
||||
} else {
|
||||
curr, ok = next(Node{})
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -57,58 +57,65 @@ func TestGraph(t *T) {
|
||||
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
|
||||
s, p := ss.(state), a.Params.(params)
|
||||
if p.add != (Edge{}) {
|
||||
s.Graph = s.Graph.AddEdge(p.add)
|
||||
s.Graph = s.Graph.Add(p.add)
|
||||
s.m[p.add.id()] = p.add
|
||||
} else {
|
||||
s.Graph = s.Graph.DelEdge(p.del)
|
||||
s.Graph = s.Graph.Del(p.del)
|
||||
delete(s.m, p.del.id())
|
||||
}
|
||||
|
||||
{ // test Values and Edges methods
|
||||
vals := s.Graph.Values()
|
||||
{ // test Nodes and Edges methods
|
||||
nodes := s.Graph.Nodes()
|
||||
edges := s.Graph.Edges()
|
||||
var aa []massert.Assertion
|
||||
found := map[string]bool{}
|
||||
tryAssert := func(v Value) {
|
||||
if ok := found[v.ID]; !ok {
|
||||
found[v.ID] = true
|
||||
aa = append(aa, massert.Has(vals, v))
|
||||
}
|
||||
}
|
||||
vals := map[string]bool{}
|
||||
ins, outs := map[string]int{}, map[string]int{}
|
||||
for _, e := range s.m {
|
||||
aa = append(aa, massert.Has(edges, e))
|
||||
tryAssert(e.Head)
|
||||
tryAssert(e.Tail)
|
||||
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(vals, len(found)))
|
||||
aa = append(aa, massert.Len(edges, len(s.m)))
|
||||
aa = append(aa, massert.Len(nodes, len(vals)))
|
||||
for id, node := range nodes {
|
||||
aa = append(aa, massert.Len(node.Ins, ins[id]))
|
||||
aa = append(aa, massert.Len(node.Outs, outs[id]))
|
||||
}
|
||||
|
||||
if err := massert.All(aa...).Assert(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
{ // test ValueEdges
|
||||
for _, val := range s.Graph.Values() {
|
||||
in, out := s.Graph.ValueEdges(val)
|
||||
var expIn, expOut []Edge
|
||||
for _, e := range s.m {
|
||||
if e.Tail.ID == val.ID {
|
||||
expOut = append(expOut, e)
|
||||
{ // test Node and Has. Nodes has already been tested so we can use
|
||||
// its returned Nodes as the expected ones
|
||||
var aa []massert.Assertion
|
||||
for _, expNode := range s.Graph.Nodes() {
|
||||
var naa []massert.Assertion
|
||||
node, ok := s.Graph.Node(expNode.Value)
|
||||
naa = append(naa, massert.Equal(true, ok))
|
||||
naa = append(naa, massert.Equal(true, s.Graph.Has(expNode.Value)))
|
||||
naa = append(naa, massert.Subset(expNode.Ins, node.Ins))
|
||||
naa = append(naa, massert.Len(node.Ins, len(expNode.Ins)))
|
||||
naa = append(naa, massert.Subset(expNode.Outs, node.Outs))
|
||||
naa = append(naa, massert.Len(node.Outs, len(expNode.Outs)))
|
||||
|
||||
aa = append(aa, massert.Comment(massert.All(naa...), "v:%q", expNode.ID))
|
||||
}
|
||||
if e.Head.ID == val.ID {
|
||||
expIn = append(expIn, e)
|
||||
}
|
||||
}
|
||||
if err := massert.Comment(massert.All(
|
||||
massert.Subset(expIn, in),
|
||||
massert.Len(in, len(expIn)),
|
||||
massert.Subset(expOut, out),
|
||||
massert.Len(out, len(expOut)),
|
||||
), "val:%q", val.V).Assert(); err != nil {
|
||||
_, ok := s.Graph.Node(strV("zz"))
|
||||
aa = append(aa, massert.Equal(false, ok))
|
||||
aa = append(aa, massert.Equal(false, s.Graph.Has(strV("zz"))))
|
||||
|
||||
if err := massert.All(aa...).Assert(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
},
|
||||
@ -147,10 +154,10 @@ func TestSubGraphAndEqual(t *T) {
|
||||
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
|
||||
s, p := ss.(state), a.Params.(params)
|
||||
if p.add1 {
|
||||
s.g1 = s.g1.AddEdge(p.e)
|
||||
s.g1 = s.g1.Add(p.e)
|
||||
}
|
||||
if p.add2 {
|
||||
s.g2 = s.g2.AddEdge(p.e)
|
||||
s.g2 = s.g2.Add(p.e)
|
||||
}
|
||||
s.expSubGraph = s.expSubGraph && p.add1
|
||||
s.expEqual = s.expEqual && p.add1 && p.add2
|
||||
|
Loading…
Reference in New Issue
Block a user