make UpEdges and DownEdges return a copy

The public functions for the graph UpEdges and DownEdges is returning
the internal Set from the graph, meaning that callers could
inadvertently corrupt the graph structure by editing the returned Sets.

Make UpEdges and DownEdges return a copy of the set, while retaining the
efficient no-copy behavior for internal callers.
This commit is contained in:
James Bardin 2020-06-11 09:49:47 -04:00
parent abedfd3a0f
commit 268959f4be
4 changed files with 42 additions and 18 deletions

View File

@ -36,7 +36,7 @@ func (g *AcyclicGraph) Ancestors(v Vertex) (Set, error) {
return nil return nil
} }
if err := g.DepthFirstWalk(g.DownEdges(v), memoFunc); err != nil { if err := g.DepthFirstWalk(g.downEdgesNoCopy(v), memoFunc); err != nil {
return nil, err return nil, err
} }
@ -52,7 +52,7 @@ func (g *AcyclicGraph) Descendents(v Vertex) (Set, error) {
return nil return nil
} }
if err := g.ReverseDepthFirstWalk(g.UpEdges(v), memoFunc); err != nil { if err := g.ReverseDepthFirstWalk(g.upEdgesNoCopy(v), memoFunc); err != nil {
return nil, err return nil, err
} }
@ -65,7 +65,7 @@ func (g *AcyclicGraph) Descendents(v Vertex) (Set, error) {
func (g *AcyclicGraph) Root() (Vertex, error) { func (g *AcyclicGraph) Root() (Vertex, error) {
roots := make([]Vertex, 0, 1) roots := make([]Vertex, 0, 1)
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
if g.UpEdges(v).Len() == 0 { if g.upEdgesNoCopy(v).Len() == 0 {
roots = append(roots, v) roots = append(roots, v)
} }
} }
@ -101,10 +101,10 @@ func (g *AcyclicGraph) TransitiveReduction() {
// //
// For each v-prime reachable from v, remove the edge (u, v-prime). // For each v-prime reachable from v, remove the edge (u, v-prime).
for _, u := range g.Vertices() { for _, u := range g.Vertices() {
uTargets := g.DownEdges(u) uTargets := g.downEdgesNoCopy(u)
g.DepthFirstWalk(g.DownEdges(u), func(v Vertex, d int) error { g.DepthFirstWalk(g.downEdgesNoCopy(u), func(v Vertex, d int) error {
shared := uTargets.Intersection(g.DownEdges(v)) shared := uTargets.Intersection(g.downEdgesNoCopy(v))
for _, vPrime := range shared { for _, vPrime := range shared {
g.RemoveEdge(BasicEdge(u, vPrime)) g.RemoveEdge(BasicEdge(u, vPrime))
} }
@ -208,7 +208,7 @@ func (g *AcyclicGraph) DepthFirstWalk(start Set, f DepthWalkFunc) error {
return err return err
} }
for _, v := range g.DownEdges(current.Vertex) { for _, v := range g.downEdgesNoCopy(current.Vertex) {
frontier = append(frontier, &vertexAtDepth{ frontier = append(frontier, &vertexAtDepth{
Vertex: v, Vertex: v,
Depth: current.Depth + 1, Depth: current.Depth + 1,
@ -248,7 +248,7 @@ func (g *AcyclicGraph) SortedDepthFirstWalk(start []Vertex, f DepthWalkFunc) err
} }
// Visit targets of this in a consistent order. // Visit targets of this in a consistent order.
targets := AsVertexList(g.DownEdges(current.Vertex)) targets := AsVertexList(g.downEdgesNoCopy(current.Vertex))
sort.Sort(byVertexName(targets)) sort.Sort(byVertexName(targets))
for _, t := range targets { for _, t := range targets {
@ -285,7 +285,7 @@ func (g *AcyclicGraph) ReverseDepthFirstWalk(start Set, f DepthWalkFunc) error {
} }
seen[current.Vertex] = struct{}{} seen[current.Vertex] = struct{}{}
for _, t := range g.UpEdges(current.Vertex) { for _, t := range g.upEdgesNoCopy(current.Vertex) {
frontier = append(frontier, &vertexAtDepth{ frontier = append(frontier, &vertexAtDepth{
Vertex: t, Vertex: t,
Depth: current.Depth + 1, Depth: current.Depth + 1,
@ -325,7 +325,7 @@ func (g *AcyclicGraph) SortedReverseDepthFirstWalk(start []Vertex, f DepthWalkFu
seen[current.Vertex] = struct{}{} seen[current.Vertex] = struct{}{}
// Add next set of targets in a consistent order. // Add next set of targets in a consistent order.
targets := AsVertexList(g.UpEdges(current.Vertex)) targets := AsVertexList(g.upEdgesNoCopy(current.Vertex))
sort.Sort(byVertexName(targets)) sort.Sort(byVertexName(targets))
for _, t := range targets { for _, t := range targets {
frontier = append(frontier, &vertexAtDepth{ frontier = append(frontier, &vertexAtDepth{

View File

@ -111,10 +111,10 @@ func (g *Graph) Remove(v Vertex) Vertex {
g.vertices.Delete(v) g.vertices.Delete(v)
// Delete the edges to non-existent things // Delete the edges to non-existent things
for _, target := range g.DownEdges(v) { for _, target := range g.downEdgesNoCopy(v) {
g.RemoveEdge(BasicEdge(v, target)) g.RemoveEdge(BasicEdge(v, target))
} }
for _, source := range g.UpEdges(v) { for _, source := range g.upEdgesNoCopy(v) {
g.RemoveEdge(BasicEdge(source, v)) g.RemoveEdge(BasicEdge(source, v))
} }
@ -137,10 +137,10 @@ func (g *Graph) Replace(original, replacement Vertex) bool {
// Add our new vertex, then copy all the edges // Add our new vertex, then copy all the edges
g.Add(replacement) g.Add(replacement)
for _, target := range g.DownEdges(original) { for _, target := range g.downEdgesNoCopy(original) {
g.Connect(BasicEdge(replacement, target)) g.Connect(BasicEdge(replacement, target))
} }
for _, source := range g.UpEdges(original) { for _, source := range g.upEdgesNoCopy(original) {
g.Connect(BasicEdge(source, replacement)) g.Connect(BasicEdge(source, replacement))
} }
@ -166,14 +166,29 @@ func (g *Graph) RemoveEdge(edge Edge) {
} }
} }
// DownEdges returns the outward edges from the source Vertex v. // UpEdges returns the vertices connected to the outward edges from the source
// Vertex v.
func (g *Graph) UpEdges(v Vertex) Set {
return g.upEdgesNoCopy(v).Copy()
}
// DownEdges returns the vertices connected from the inward edges to Vertex v.
func (g *Graph) DownEdges(v Vertex) Set { func (g *Graph) DownEdges(v Vertex) Set {
return g.downEdgesNoCopy(v).Copy()
}
// downEdgesNoCopy returns the outward edges from the source Vertex v as a Set.
// This Set is the same as used internally bu the Graph to prevent a copy, and
// must not be modified by the caller.
func (g *Graph) downEdgesNoCopy(v Vertex) Set {
g.init() g.init()
return g.downEdges[hashcode(v)] return g.downEdges[hashcode(v)]
} }
// UpEdges returns the inward edges to the destination Vertex v. // upEdgesNoCopy returns the inward edges to the destination Vertex v as a Set.
func (g *Graph) UpEdges(v Vertex) Set { // This Set is the same as used internally bu the Graph to prevent a copy, and
// must not be modified by the caller.
func (g *Graph) upEdgesNoCopy(v Vertex) Set {
g.init() g.init()
return g.upEdges[hashcode(v)] return g.upEdges[hashcode(v)]
} }

View File

@ -103,3 +103,12 @@ func (s Set) List() []interface{} {
return r return r
} }
// Copy returns a shallow copy of the set.
func (s Set) Copy() Set {
c := make(Set)
for k, v := range s {
c[k] = v
}
return c
}

View File

@ -24,7 +24,7 @@ func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int {
index := acct.visit(v) index := acct.visit(v)
minIdx := index minIdx := index
for _, raw := range g.DownEdges(v) { for _, raw := range g.downEdgesNoCopy(v) {
target := raw.(Vertex) target := raw.(Vertex)
targetIdx := acct.VertexIndex[target] targetIdx := acct.VertexIndex[target]