pgraph, resources: Major refactor to remove pgraph to resource dep
This is the mechanical port of the remaining bits. Next to clean it up a bit.
This commit is contained in:
260
pgraph/pgraph.go
260
pgraph/pgraph.go
@@ -21,10 +21,6 @@ package pgraph
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/event"
|
||||
"github.com/purpleidea/mgmt/resources"
|
||||
|
||||
errwrap "github.com/pkg/errors"
|
||||
)
|
||||
@@ -38,17 +34,14 @@ import (
|
||||
type Graph struct {
|
||||
Name string
|
||||
|
||||
adjacency map[*Vertex]map[*Vertex]*Edge // *Vertex -> *Vertex (edge)
|
||||
kv map[string]interface{} // some values associated with the graph
|
||||
|
||||
// legacy
|
||||
fastPause bool // used to disable pokes for a fast pause
|
||||
wg *sync.WaitGroup
|
||||
adjacency map[Vertex]map[Vertex]*Edge // Vertex -> Vertex (edge)
|
||||
kv map[string]interface{} // some values associated with the graph
|
||||
}
|
||||
|
||||
// Vertex is the primary vertex struct in this library.
|
||||
type Vertex struct {
|
||||
resources.Res // anonymous field
|
||||
// Vertex is the primary vertex struct in this library. It can be anything that
|
||||
// implements Stringer. The string output must be stable and unique in a graph.
|
||||
type Vertex interface {
|
||||
fmt.Stringer // String() string
|
||||
}
|
||||
|
||||
// Edge is the primary edge struct in this library.
|
||||
@@ -65,12 +58,8 @@ func (g *Graph) Init() error {
|
||||
return fmt.Errorf("can't initialize graph with empty name")
|
||||
}
|
||||
|
||||
g.adjacency = make(map[*Vertex]map[*Vertex]*Edge)
|
||||
g.adjacency = make(map[Vertex]map[Vertex]*Edge)
|
||||
g.kv = make(map[string]interface{})
|
||||
|
||||
// legacy
|
||||
// ptr b/c: Mutex/WaitGroup must not be copied after first use
|
||||
g.wg = &sync.WaitGroup{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -85,11 +74,11 @@ func NewGraph(name string) (*Graph, error) {
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// NewVertex returns a new graph vertex struct with a contained resource.
|
||||
func NewVertex(r resources.Res) *Vertex {
|
||||
return &Vertex{
|
||||
Res: r,
|
||||
}
|
||||
// NewVertex returns whatever was passed in. This is for compatibility with the
|
||||
// usage of the old NewVertex method. This is considered deprecated.
|
||||
// FIXME: remove me
|
||||
func NewVertex(x Vertex) Vertex {
|
||||
return x
|
||||
}
|
||||
|
||||
// NewEdge returns a new graph edge struct.
|
||||
@@ -120,16 +109,12 @@ func (g *Graph) SetValue(key string, val interface{}) {
|
||||
g.kv[key] = val
|
||||
}
|
||||
|
||||
// Copy makes a copy of the graph struct
|
||||
// Copy makes a copy of the graph struct.
|
||||
func (g *Graph) Copy() *Graph {
|
||||
newGraph := &Graph{
|
||||
Name: g.Name,
|
||||
adjacency: make(map[*Vertex]map[*Vertex]*Edge, len(g.adjacency)),
|
||||
adjacency: make(map[Vertex]map[Vertex]*Edge, len(g.adjacency)),
|
||||
kv: g.kv,
|
||||
|
||||
// legacy
|
||||
wg: g.wg,
|
||||
fastPause: g.fastPause,
|
||||
}
|
||||
for k, v := range g.adjacency {
|
||||
newGraph.adjacency[k] = v // copy
|
||||
@@ -147,17 +132,17 @@ func (g *Graph) SetName(name string) {
|
||||
g.Name = name
|
||||
}
|
||||
|
||||
// AddVertex uses variadic input to add all listed vertices to the graph
|
||||
func (g *Graph) AddVertex(xv ...*Vertex) {
|
||||
// AddVertex uses variadic input to add all listed vertices to the graph.
|
||||
func (g *Graph) AddVertex(xv ...Vertex) {
|
||||
for _, v := range xv {
|
||||
if _, exists := g.adjacency[v]; !exists {
|
||||
g.adjacency[v] = make(map[*Vertex]*Edge)
|
||||
g.adjacency[v] = make(map[Vertex]*Edge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteVertex deletes a particular vertex from the graph.
|
||||
func (g *Graph) DeleteVertex(v *Vertex) {
|
||||
func (g *Graph) DeleteVertex(v Vertex) {
|
||||
delete(g.adjacency, v)
|
||||
for k := range g.adjacency {
|
||||
delete(g.adjacency[k], v)
|
||||
@@ -165,7 +150,7 @@ func (g *Graph) DeleteVertex(v *Vertex) {
|
||||
}
|
||||
|
||||
// AddEdge adds a directed edge to the graph from v1 to v2.
|
||||
func (g *Graph) AddEdge(v1, v2 *Vertex, e *Edge) {
|
||||
func (g *Graph) AddEdge(v1, v2 Vertex, e *Edge) {
|
||||
// NOTE: this doesn't allow more than one edge between two vertexes...
|
||||
g.AddVertex(v1, v2) // supports adding N vertices now
|
||||
// TODO: check if an edge exists to avoid overwriting it!
|
||||
@@ -188,7 +173,7 @@ func (g *Graph) DeleteEdge(e *Edge) {
|
||||
// VertexMatchFn searches for a vertex in the graph and returns the vertex if
|
||||
// one matches. It uses a user defined function to match. That function must
|
||||
// return true on match, and an error if anything goes wrong.
|
||||
func (g *Graph) VertexMatchFn(fn func(*Vertex) (bool, error)) (*Vertex, error) {
|
||||
func (g *Graph) VertexMatchFn(fn func(Vertex) (bool, error)) (Vertex, error) {
|
||||
for v := range g.adjacency {
|
||||
if b, err := fn(v); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "fn in VertexMatchFn() errored")
|
||||
@@ -199,19 +184,8 @@ func (g *Graph) VertexMatchFn(fn func(*Vertex) (bool, error)) (*Vertex, error) {
|
||||
return nil, nil // nothing found
|
||||
}
|
||||
|
||||
// TODO: consider adding a mutate API.
|
||||
//func (g *Graph) MutateMatch(obj resources.Res) *Vertex {
|
||||
// for v := range g.adjacency {
|
||||
// if err := v.Res.Mutate(obj); err == nil {
|
||||
// // transmogrified!
|
||||
// return v
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// HasVertex returns if the input vertex exists in the graph.
|
||||
func (g *Graph) HasVertex(v *Vertex) bool {
|
||||
func (g *Graph) HasVertex(v Vertex) bool {
|
||||
if _, exists := g.adjacency[v]; exists {
|
||||
return true
|
||||
}
|
||||
@@ -234,14 +208,15 @@ func (g *Graph) NumEdges() int {
|
||||
|
||||
// Adjacency returns the adjacency map representing this graph. This is useful
|
||||
// for users who which to operate on the raw data structure more efficiently.
|
||||
func (g *Graph) Adjacency() map[*Vertex]map[*Vertex]*Edge {
|
||||
// This works because maps are reference types so we can edit this at will.
|
||||
func (g *Graph) Adjacency() map[Vertex]map[Vertex]*Edge {
|
||||
return g.adjacency
|
||||
}
|
||||
|
||||
// Vertices returns a randomly sorted slice of all vertices in the graph.
|
||||
// The order is random, because the map implementation is intentionally so!
|
||||
func (g *Graph) Vertices() []*Vertex {
|
||||
var vertices []*Vertex
|
||||
func (g *Graph) Vertices() []Vertex {
|
||||
var vertices []Vertex
|
||||
for k := range g.adjacency {
|
||||
vertices = append(vertices, k)
|
||||
}
|
||||
@@ -249,9 +224,9 @@ func (g *Graph) Vertices() []*Vertex {
|
||||
}
|
||||
|
||||
// VerticesChan returns a channel of all vertices in the graph.
|
||||
func (g *Graph) VerticesChan() chan *Vertex {
|
||||
ch := make(chan *Vertex)
|
||||
go func(ch chan *Vertex) {
|
||||
func (g *Graph) VerticesChan() chan Vertex {
|
||||
ch := make(chan Vertex)
|
||||
go func(ch chan Vertex) {
|
||||
for k := range g.adjacency {
|
||||
ch <- k
|
||||
}
|
||||
@@ -261,7 +236,7 @@ func (g *Graph) VerticesChan() chan *Vertex {
|
||||
}
|
||||
|
||||
// VertexSlice is a linear list of vertices. It can be sorted.
|
||||
type VertexSlice []*Vertex
|
||||
type VertexSlice []Vertex
|
||||
|
||||
func (vs VertexSlice) Len() int { return len(vs) }
|
||||
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
@@ -269,8 +244,8 @@ func (vs VertexSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String
|
||||
|
||||
// VerticesSorted returns a sorted slice of all vertices in the graph
|
||||
// The order is sorted by String() to avoid the non-determinism in the map type
|
||||
func (g *Graph) VerticesSorted() []*Vertex {
|
||||
var vertices []*Vertex
|
||||
func (g *Graph) VerticesSorted() []Vertex {
|
||||
var vertices []Vertex
|
||||
for k := range g.adjacency {
|
||||
vertices = append(vertices, k)
|
||||
}
|
||||
@@ -283,17 +258,12 @@ func (g *Graph) String() string {
|
||||
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
||||
}
|
||||
|
||||
// String returns the canonical form for a vertex
|
||||
func (v *Vertex) String() string {
|
||||
return fmt.Sprintf("%s[%s]", v.Res.GetKind(), v.Res.GetName())
|
||||
}
|
||||
|
||||
// IncomingGraphVertices returns an array (slice) of all directed vertices to
|
||||
// vertex v (??? -> v). OKTimestamp should probably use this.
|
||||
func (g *Graph) IncomingGraphVertices(v *Vertex) []*Vertex {
|
||||
func (g *Graph) IncomingGraphVertices(v Vertex) []Vertex {
|
||||
// TODO: we might be able to implement this differently by reversing
|
||||
// the Adjacency graph and then looping through it again...
|
||||
var s []*Vertex
|
||||
var s []Vertex
|
||||
for k := range g.adjacency { // reverse paths
|
||||
for w := range g.adjacency[k] {
|
||||
if w == v {
|
||||
@@ -306,8 +276,8 @@ func (g *Graph) IncomingGraphVertices(v *Vertex) []*Vertex {
|
||||
|
||||
// OutgoingGraphVertices returns an array (slice) of all vertices that vertex v
|
||||
// points to (v -> ???). Poke should probably use this.
|
||||
func (g *Graph) OutgoingGraphVertices(v *Vertex) []*Vertex {
|
||||
var s []*Vertex
|
||||
func (g *Graph) OutgoingGraphVertices(v Vertex) []Vertex {
|
||||
var s []Vertex
|
||||
for k := range g.adjacency[v] { // forward paths
|
||||
s = append(s, k)
|
||||
}
|
||||
@@ -316,15 +286,15 @@ func (g *Graph) OutgoingGraphVertices(v *Vertex) []*Vertex {
|
||||
|
||||
// GraphVertices returns an array (slice) of all vertices that connect to vertex v.
|
||||
// This is the union of IncomingGraphVertices and OutgoingGraphVertices.
|
||||
func (g *Graph) GraphVertices(v *Vertex) []*Vertex {
|
||||
var s []*Vertex
|
||||
func (g *Graph) GraphVertices(v Vertex) []Vertex {
|
||||
var s []Vertex
|
||||
s = append(s, g.IncomingGraphVertices(v)...)
|
||||
s = append(s, g.OutgoingGraphVertices(v)...)
|
||||
return s
|
||||
}
|
||||
|
||||
// IncomingGraphEdges returns all of the edges that point to vertex v (??? -> v).
|
||||
func (g *Graph) IncomingGraphEdges(v *Vertex) []*Edge {
|
||||
func (g *Graph) IncomingGraphEdges(v Vertex) []*Edge {
|
||||
var edges []*Edge
|
||||
for v1 := range g.adjacency { // reverse paths
|
||||
for v2, e := range g.adjacency[v1] {
|
||||
@@ -337,7 +307,7 @@ func (g *Graph) IncomingGraphEdges(v *Vertex) []*Edge {
|
||||
}
|
||||
|
||||
// OutgoingGraphEdges returns all of the edges that point from vertex v (v -> ???).
|
||||
func (g *Graph) OutgoingGraphEdges(v *Vertex) []*Edge {
|
||||
func (g *Graph) OutgoingGraphEdges(v Vertex) []*Edge {
|
||||
var edges []*Edge
|
||||
for _, e := range g.adjacency[v] { // forward paths
|
||||
edges = append(edges, e)
|
||||
@@ -347,7 +317,7 @@ func (g *Graph) OutgoingGraphEdges(v *Vertex) []*Edge {
|
||||
|
||||
// GraphEdges returns an array (slice) of all edges that connect to vertex v.
|
||||
// This is the union of IncomingGraphEdges and OutgoingGraphEdges.
|
||||
func (g *Graph) GraphEdges(v *Vertex) []*Edge {
|
||||
func (g *Graph) GraphEdges(v Vertex) []*Edge {
|
||||
var edges []*Edge
|
||||
edges = append(edges, g.IncomingGraphEdges(v)...)
|
||||
edges = append(edges, g.OutgoingGraphEdges(v)...)
|
||||
@@ -355,9 +325,9 @@ func (g *Graph) GraphEdges(v *Vertex) []*Edge {
|
||||
}
|
||||
|
||||
// DFS returns a depth first search for the graph, starting at the input vertex.
|
||||
func (g *Graph) DFS(start *Vertex) []*Vertex {
|
||||
var d []*Vertex // discovered
|
||||
var s []*Vertex // stack
|
||||
func (g *Graph) DFS(start Vertex) []Vertex {
|
||||
var d []Vertex // discovered
|
||||
var s []Vertex // stack
|
||||
if _, exists := g.adjacency[start]; !exists {
|
||||
return nil // TODO: error
|
||||
}
|
||||
@@ -378,7 +348,7 @@ func (g *Graph) DFS(start *Vertex) []*Vertex {
|
||||
}
|
||||
|
||||
// FilterGraph builds a new graph containing only vertices from the list.
|
||||
func (g *Graph) FilterGraph(name string, vertices []*Vertex) (*Graph, error) {
|
||||
func (g *Graph) FilterGraph(name string, vertices []Vertex) (*Graph, error) {
|
||||
newGraph := &Graph{Name: name}
|
||||
if err := newGraph.Init(); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not run FilterGraph() properly")
|
||||
@@ -397,8 +367,8 @@ func (g *Graph) FilterGraph(name string, vertices []*Vertex) (*Graph, error) {
|
||||
// DisconnectedGraphs returns a list containing the N disconnected graphs.
|
||||
func (g *Graph) DisconnectedGraphs() ([]*Graph, error) {
|
||||
graphs := []*Graph{}
|
||||
var start *Vertex
|
||||
var d []*Vertex // discovered
|
||||
var start Vertex
|
||||
var d []Vertex // discovered
|
||||
c := g.NumVertices()
|
||||
for len(d) < c {
|
||||
|
||||
@@ -429,8 +399,8 @@ func (g *Graph) DisconnectedGraphs() ([]*Graph, error) {
|
||||
}
|
||||
|
||||
// InDegree returns the count of vertices that point to me in one big lookup map.
|
||||
func (g *Graph) InDegree() map[*Vertex]int {
|
||||
result := make(map[*Vertex]int)
|
||||
func (g *Graph) InDegree() map[Vertex]int {
|
||||
result := make(map[Vertex]int)
|
||||
for k := range g.adjacency {
|
||||
result[k] = 0 // initialize
|
||||
}
|
||||
@@ -444,8 +414,8 @@ func (g *Graph) InDegree() map[*Vertex]int {
|
||||
}
|
||||
|
||||
// OutDegree returns the count of vertices that point away in one big lookup map.
|
||||
func (g *Graph) OutDegree() map[*Vertex]int {
|
||||
result := make(map[*Vertex]int)
|
||||
func (g *Graph) OutDegree() map[Vertex]int {
|
||||
result := make(map[Vertex]int)
|
||||
|
||||
for k := range g.adjacency {
|
||||
result[k] = 0 // initialize
|
||||
@@ -457,12 +427,12 @@ func (g *Graph) OutDegree() map[*Vertex]int {
|
||||
}
|
||||
|
||||
// TopologicalSort returns the sort of graph vertices in that order.
|
||||
// based on descriptions and code from wikipedia and rosetta code
|
||||
// It is based on descriptions and code from wikipedia and rosetta code.
|
||||
// TODO: add memoization, and cache invalidation to speed this up :)
|
||||
func (g *Graph) TopologicalSort() ([]*Vertex, error) { // kahn's algorithm
|
||||
var L []*Vertex // empty list that will contain the sorted elements
|
||||
var S []*Vertex // set of all nodes with no incoming edges
|
||||
remaining := make(map[*Vertex]int) // amount of edges remaining
|
||||
func (g *Graph) TopologicalSort() ([]Vertex, error) { // kahn's algorithm
|
||||
var L []Vertex // empty list that will contain the sorted elements
|
||||
var S []Vertex // set of all nodes with no incoming edges
|
||||
remaining := make(map[Vertex]int) // amount of edges remaining
|
||||
|
||||
for v, d := range g.InDegree() {
|
||||
if d == 0 {
|
||||
@@ -513,19 +483,19 @@ func (g *Graph) TopologicalSort() ([]*Vertex, error) { // kahn's algorithm
|
||||
// actually return a tree if we cared about correctness.
|
||||
// This operates by a recursive algorithm; a more efficient version is likely.
|
||||
// If you don't give this function a DAG, you might cause infinite recursion!
|
||||
func (g *Graph) Reachability(a, b *Vertex) []*Vertex {
|
||||
func (g *Graph) Reachability(a, b Vertex) []Vertex {
|
||||
if a == nil || b == nil {
|
||||
return nil
|
||||
}
|
||||
vertices := g.OutgoingGraphVertices(a) // what points away from a ?
|
||||
if len(vertices) == 0 {
|
||||
return []*Vertex{} // nope
|
||||
return []Vertex{} // nope
|
||||
}
|
||||
if VertexContains(b, vertices) {
|
||||
return []*Vertex{a, b} // found
|
||||
return []Vertex{a, b} // found
|
||||
}
|
||||
// TODO: parallelize this with go routines?
|
||||
var collected = make([][]*Vertex, len(vertices))
|
||||
var collected = make([][]Vertex, len(vertices))
|
||||
pick := -1
|
||||
for i, v := range vertices {
|
||||
collected[i] = g.Reachability(v, b) // find b by recursion
|
||||
@@ -538,116 +508,15 @@ func (g *Graph) Reachability(a, b *Vertex) []*Vertex {
|
||||
}
|
||||
}
|
||||
if pick < 0 {
|
||||
return []*Vertex{} // nope
|
||||
return []Vertex{} // nope
|
||||
}
|
||||
result := []*Vertex{a} // tack on a
|
||||
result := []Vertex{a} // tack on a
|
||||
result = append(result, collected[pick]...)
|
||||
return result
|
||||
}
|
||||
|
||||
// GraphSync updates the oldGraph so that it matches the newGraph receiver. It
|
||||
// leaves identical elements alone so that they don't need to be refreshed. It
|
||||
// tries to mutate existing elements into new ones, if they support this.
|
||||
// FIXME: add test cases
|
||||
func (g *Graph) GraphSync(oldGraph *Graph) (*Graph, error) {
|
||||
|
||||
if oldGraph == nil {
|
||||
var err error
|
||||
oldGraph, err = NewGraph(g.GetName()) // copy over the name
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not run GraphSync() properly")
|
||||
}
|
||||
}
|
||||
oldGraph.SetName(g.GetName()) // overwrite the name
|
||||
|
||||
var lookup = make(map[*Vertex]*Vertex)
|
||||
var vertexKeep []*Vertex // list of vertices which are the same in new graph
|
||||
var edgeKeep []*Edge // list of vertices which are the same in new graph
|
||||
|
||||
for v := range g.adjacency { // loop through the vertices (resources)
|
||||
res := v.Res // resource
|
||||
var vertex *Vertex
|
||||
|
||||
// step one, direct compare with res.Compare
|
||||
if vertex == nil { // redundant guard for consistency
|
||||
fn := func(v *Vertex) (bool, error) {
|
||||
return v.Res.Compare(res), nil
|
||||
}
|
||||
var err error
|
||||
vertex, err = oldGraph.VertexMatchFn(fn)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not VertexMatchFn() resource")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider adding a mutate API.
|
||||
// step two, try and mutate with res.Mutate
|
||||
//if vertex == nil { // not found yet...
|
||||
// vertex = oldGraph.MutateMatch(res)
|
||||
//}
|
||||
|
||||
if vertex == nil { // no match found yet
|
||||
if err := res.Validate(); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not Validate() resource")
|
||||
}
|
||||
vertex = v
|
||||
oldGraph.AddVertex(vertex) // call standalone in case not part of an edge
|
||||
}
|
||||
lookup[v] = vertex // used for constructing edges
|
||||
vertexKeep = append(vertexKeep, vertex) // append
|
||||
}
|
||||
|
||||
// get rid of any vertices we shouldn't keep (that aren't in new graph)
|
||||
for v := range oldGraph.adjacency {
|
||||
if !VertexContains(v, vertexKeep) {
|
||||
// wait for exit before starting new graph!
|
||||
v.SendEvent(event.EventExit, nil) // sync
|
||||
v.Res.WaitGroup().Wait()
|
||||
oldGraph.DeleteVertex(v)
|
||||
}
|
||||
}
|
||||
|
||||
// compare edges
|
||||
for v1 := range g.adjacency { // loop through the vertices (resources)
|
||||
for v2, e := range g.adjacency[v1] {
|
||||
// we have an edge!
|
||||
|
||||
// lookup vertices (these should exist now)
|
||||
//res1 := v1.Res // resource
|
||||
//res2 := v2.Res
|
||||
//vertex1 := oldGraph.CompareMatch(res1) // now: VertexMatchFn
|
||||
//vertex2 := oldGraph.CompareMatch(res2) // now: VertexMatchFn
|
||||
vertex1, exists1 := lookup[v1]
|
||||
vertex2, exists2 := lookup[v2]
|
||||
if !exists1 || !exists2 { // no match found, bug?
|
||||
//if vertex1 == nil || vertex2 == nil { // no match found
|
||||
return nil, fmt.Errorf("new vertices weren't found") // programming error
|
||||
}
|
||||
|
||||
edge, exists := oldGraph.adjacency[vertex1][vertex2]
|
||||
if !exists || edge.Name != e.Name { // TODO: edgeCmp
|
||||
edge = e // use or overwrite edge
|
||||
}
|
||||
oldGraph.adjacency[vertex1][vertex2] = edge // store it (AddEdge)
|
||||
edgeKeep = append(edgeKeep, edge) // mark as saved
|
||||
}
|
||||
}
|
||||
|
||||
// delete unused edges
|
||||
for v1 := range oldGraph.adjacency {
|
||||
for _, e := range oldGraph.adjacency[v1] {
|
||||
// we have an edge!
|
||||
if !EdgeContains(e, edgeKeep) {
|
||||
oldGraph.DeleteEdge(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oldGraph, nil
|
||||
}
|
||||
|
||||
// VertexContains is an "in array" function to test for a vertex in a slice of vertices.
|
||||
func VertexContains(needle *Vertex, haystack []*Vertex) bool {
|
||||
func VertexContains(needle Vertex, haystack []Vertex) bool {
|
||||
for _, v := range haystack {
|
||||
if needle == v {
|
||||
return true
|
||||
@@ -667,9 +536,8 @@ func EdgeContains(needle *Edge, haystack []*Edge) bool {
|
||||
}
|
||||
|
||||
// Reverse reverses a list of vertices.
|
||||
func Reverse(vs []*Vertex) []*Vertex {
|
||||
//var out []*Vertex // XXX: golint suggests, but it fails testing
|
||||
out := make([]*Vertex, 0) // empty list
|
||||
func Reverse(vs []Vertex) []Vertex {
|
||||
out := []Vertex{}
|
||||
l := len(vs)
|
||||
for i := range vs {
|
||||
out = append(out, vs[l-i-1])
|
||||
|
||||
Reference in New Issue
Block a user