Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package utils | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "fmt" | |
| 10 ) | |
| 11 | |
| 12 // This file contains an implementation of an algorithm to check the well-founde dness | |
| 13 // of two-sorted directed graphs. See the paper | |
| 14 // "Well-Founded Two-Sorted Directed Graphs" (https://goo.gl/ipxFKu) for backgro und | |
| 15 // and detailed definitions. Here we give only a high-level overview. | |
| 16 // | |
| 17 // A two-sorted graph is a directed graph that contains two sorts of nodes calle d circle nodes and | |
| 18 // square nodes. A node in a two-sorted graph is *well-founded* iff it satisfies the | |
| 19 // following recursive definition: | |
| 20 // (i) Leaf nodes are well-founded | |
| 21 // (ii) A circle node is well-founded iff all of its children are well-founded | |
| 22 // (iii) A non-leaf square node is well-founded iff at least one of its children is well-founded. | |
| 23 // (See the paper for a more logically correct definition.) | |
| 24 // | |
| 25 // See the comments on the main function |CheckWellFounded| below for a descript ion | |
| 26 // of the algorithm. | |
| 27 | |
| 28 /////////////////////////////////////////////////// | |
| 29 // Node type | |
| 30 // | |
| 31 // A |Node| is a node in a directed graph. | |
| 32 /////////////////////////////////////////////////// | |
| 33 | |
| 34 type Node interface { | |
| 35 // Returns the list of children of this node. | |
| 36 OutEdges() []Node | |
| 37 | |
| 38 // Returns whether or not this node is a square node. | |
| 39 IsSquare() bool | |
| 40 | |
| 41 // SetKnownWellFounded is invoked by the algorithm in order to set the | |
| 42 // fact that the algorithm has determined that this node is well-founded . | |
| 43 // An implementation of |Node| should cache this--even between different | |
| 44 // invocations of the algorithm on different starting nodes--and return | |
| 45 // |true| from the method |KnownWellFounded| just in case this method ha s | |
| 46 // ever been invoked. In this way any nodes verified to be well-founded during | |
| 47 // one run of the algorithm do not need to be checked during a later run of the | |
| 48 // algorithm on a different starting node. | |
| 49 SetKnownWellFounded() | |
| 50 | |
| 51 // Returns whether or not the function |SetKnownWellFounded| has ever | |
| 52 // been invoked on this node, during the lifetime of a program using | |
| 53 // this library. | |
| 54 KnownWellFounded() bool | |
| 55 | |
| 56 // Returns the name of this node, appropriate for debugging. | |
| 57 DebugName() string | |
| 58 } | |
| 59 | |
| 60 /////////////////////////////////////////////////// | |
| 61 // CheckWellFounded function | |
| 62 // | |
| 63 // This is the main public function. | |
| 64 /////////////////////////////////////////////////// | |
| 65 | |
| 66 // CheckWellFounded checks whether the sub-graph rooted at |root| contains any i ll-founded nodes. | |
| 67 // If any ill-founded nodes are detected then a non-nil |CycleDescription| is re turned containing | |
| 68 // a cycle of ill-founded nodes. If every node is well-founded then nil is retur ned. | |
| 69 // | |
| 70 // If the graph contains only circle nodes then well-foundedness is equivalent t o | |
| 71 // acyclicality and so the returned cycle is a proof that the nodes contained in it | |
| 72 // are ill-founded. But in general (if the graph contains square nodes) the retu rned | |
| 73 // |CycleDescription| is only meant to serve as an example of some of the | |
| 74 // ill-foundedness contained in the subgraph. The cycle is guaranteed to contain only | |
| 75 // ill-founded nodes and the cycle may be considered part of a proof that thes n odes | |
| 76 // are in fact ill-founded. But the cycle does not necessarily contain every | |
| 77 // ill-founded node and it does not | |
| 78 // necessarily constitute a complete proof of the ill-foundedness | |
| 79 // because a graph that contains square nodes is allowed to contain some cycles and still be | |
| 80 // well-founded. The intended application of the returned CycleDescription is to be used to | |
| 81 // describe to a user the location of the ill-foundedness in order to allow the user to modify | |
| 82 // the graph in order to make it well-founded. | |
| 83 // | |
| 84 // This function may be invoked multiple times on different starting nodes durin g the life | |
| 85 // of a program. If the function is invoked once on node |x| and no ill-foundedn ess is | |
| 86 // found and then the function is invoked later on node |y| and if node |z| is r eachable | |
| 87 // from both |x| and |y| then node |z| will be marked |KnownWellFounded| during the | |
| 88 // first run of the function and so the graph below |z| will not have to be insp ected | |
| 89 // during the second run of the function. | |
| 90 // | |
| 91 // Our algorithm proceeds in three phases: | |
| 92 // (1) Phase 1 consists of a depth-first traversal of the graph whose purpose is two-fold: | |
| 93 // (a) To prove directly that as many nodes as possible are well-founded and mark them | |
| 94 // so by invoking SetKnownWellFounded(). | |
| 95 // (b) To prepare for phase 2 by constructing two sets of nodes called the | foundationSet| and | |
| 96 // the |pendingSet| | |
| 97 // See the comments at |checkWellFoundedPhase1| for more details. | |
| 98 // | |
| 99 // In many cases phase 1 will be able to prove that every node is well-founded a nd so the algorithm | |
| 100 // will terminate without entering phase 2. This is the case for example if the graph has no | |
| 101 // cycles at all. | |
| 102 // | |
| 103 // (2) The purpose of phase 2 is to propogate the |KnownWellFounded| property to the remaining well-founded | |
| 104 // nodes (the ones that could not be verified as well-founded during phase 1 .) Phase 2 proceeds in | |
| 105 // multiple rounds. During each round the |KnownWellFounded| property is pro pogated from the | |
| 106 // |foundationSet| to the |pendingSet|. See the comments at |checkWellFounde dPhase2| for more details. | |
| 107 // | |
| 108 // If there are no ill-founded nodes then the algorithm terminates after phase 2. | |
| 109 // | |
| 110 // (3) Phase 3 of the algorithm consists of building a |CycleDescription| in the case that there are ill-founded | |
| 111 // nodes. See the method |findKnownCycle|. | |
| 112 | |
| 113 func CheckWellFounded(root Node) *CycleDescription { | |
| 114 return checkWellFounded(root, nil) | |
| 115 } | |
| 116 | |
| 117 // checkWellFounded is a package-private version of CheckWellFounded intendend t o be invoked by tests. | |
| 118 // It offers a second parameter |debugDataRequest|. If this is non-nil then its fields will be filled | |
| 119 // in with debugging data about the output of phase 1. | |
| 120 func checkWellFounded(root Node, debugDataRequest *debugData) *CycleDescription { | |
| 121 if root == nil { | |
| 122 return nil | |
| 123 } | |
| 124 finder := makeCycleFinder() | |
| 125 finder.debugDataRequest = debugDataRequest | |
| 126 holder := finder.holderForNode(root) | |
| 127 return finder.checkWellFounded(holder) | |
| 128 } | |
| 129 | |
| 130 /////////////////////////////////////////////////// | |
| 131 /// cycleFinder type | |
| 132 /// | |
| 133 /// A cycleFinder is an object that holds state during the execution of the algo rithm. | |
| 134 /////////////////////////////////////////////////// | |
| 135 type cycleFinder struct { | |
| 136 | |
| 137 // Maps each node seen by the computation to its holder. | |
| 138 nodeToHolder map[Node]*nodeHolder | |
| 139 | |
| 140 foundationSet, pendingSet NodeSet | |
| 141 | |
| 142 visitationIndex int | |
| 143 currentPath nodeStack | |
| 144 | |
| 145 debugDataRequest *debugData | |
| 146 } | |
| 147 | |
| 148 type debugData struct { | |
| 149 initialPendingSet []Node | |
| 150 initialFoundationSet []Node | |
| 151 } | |
| 152 | |
| 153 func makeCycleFinder() cycleFinder { | |
| 154 finder := cycleFinder{} | |
| 155 finder.nodeToHolder = make(map[Node]*nodeHolder) | |
| 156 finder.foundationSet = MakeNodeSet() | |
| 157 finder.pendingSet = MakeNodeSet() | |
| 158 finder.currentPath = nodeStack{} | |
| 159 return finder | |
| 160 } | |
| 161 | |
| 162 // checkWellFounded contains the top-level implementation of the algorithm. | |
| 163 func (finder *cycleFinder) checkWellFounded(nodeHolder *nodeHolder) *CycleDescri ption { | |
| 164 if nodeHolder.node.KnownWellFounded() { | |
| 165 // This node is already known to be well-founded because of an e arlier | |
| 166 // execution of the algorithm. | |
| 167 return nil | |
| 168 } | |
| 169 | |
| 170 // Perform phase 1. | |
| 171 finder.checkWellFoundedPhase1(nodeHolder) | |
| 172 | |
| 173 // In tests we pass back some debugging information here. | |
| 174 if finder.debugDataRequest != nil { | |
| 175 finder.debugDataRequest.initialPendingSet = finder.pendingSet.To NodeSlice() | |
| 176 finder.debugDataRequest.initialFoundationSet = finder.foundation Set.ToNodeSlice() | |
| 177 } | |
| 178 | |
| 179 // All nodes have been verified as well-founded. | |
| 180 if finder.pendingSet.Empty() { | |
| 181 return nil | |
| 182 } | |
| 183 | |
| 184 // Perform phase 2. | |
| 185 finder.checkWellFoundedPhase2(nodeHolder) | |
| 186 | |
| 187 // All nodes have been verified as well-founded. | |
| 188 if finder.pendingSet.Empty() { | |
| 189 return nil | |
| 190 } | |
| 191 | |
| 192 // If we are here then there is at least one ill-founded node. | |
| 193 | |
| 194 // In order to build a canonical cycle description, find the illfounded | |
| 195 // node with the least visitation order. | |
| 196 var minVisitationOrder int | |
| 197 var minIllfoundedNode Node = nil | |
| 198 for n, _ := range finder.pendingSet.elements { | |
| 199 if minIllfoundedNode == nil || n.visitationOrder < minVisitation Order { | |
| 200 minVisitationOrder = n.visitationOrder | |
| 201 minIllfoundedNode = n.node | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 // Starting from the ill-founded node with the least visitation order, | |
| 206 // build a canonical cycle. | |
| 207 freshCycleFinder := makeCycleFinder() | |
| 208 holder := freshCycleFinder.holderForNode(minIllfoundedNode) | |
| 209 return freshCycleFinder.findKnownCycle(holder) | |
| 210 } | |
| 211 | |
| 212 // checkWellFoundedPhase1 is a recursive helper function that does a depth-first traversal | |
| 213 // of the graph rooted at |nodeHolder|. The goal of phase 1 is to mark as many | |
| 214 // of the nodes as possible as |KnownWellFounded| and to set up the |pendingSet| , | |
| 215 // the |foundationSet|, and the |parentsToBeNotified| sets so that the remaining | |
| 216 // well-founded nodes will be marked as |KnownWellFounded| in phase 2. | |
| 217 // | |
| 218 // In more detail the following steps are performed for each node x: | |
| 219 // (a1) If it can be verified during the traversal that x is well-founded then | |
| 220 // x will be marked as |KnownWellFounded|. This occurs if x is a leaf, or x | |
| 221 // is a circle and each child node of x is known well-founded before being | |
| 222 // visited as a child of x, or x is a square and at-least one child node of x | |
| 223 // is known well-founded before being visited as a child of x. | |
| 224 // (a2) Otherwise if it cannot be determined during traveral that x is well-foun ded then | |
| 225 // (i) x is added to the |pendingSet|. | |
| 226 // (ii) x is added to the |parentsToBeNotified| set of all of its children. | |
| 227 // (b) In step (a1) if at the time x is found to be well-founded x already has | |
| 228 // some parent node x' in its |parentsToBeNotified| set (meaning that step a2 oc curred | |
| 229 // earlier for x' and so x' is in the |pendingSet|) then x is added to the |foun dationSet|. | |
| 230 // In phase 2, the fact that x is in the foundation set and x' is in the pending set will be | |
| 231 // used to propogate known-wellfoundedness to x'. | |
| 232 func (finder *cycleFinder) checkWellFoundedPhase1(nodeHolder *nodeHolder) { | |
| 233 if nodeHolder.node.KnownWellFounded() { | |
| 234 // This node is known to be well-founded before being visited. | |
| 235 // This occurs when the node was marked |KnownWellFounded| durin g a | |
| 236 // previous run of the algorithm. It follows that all nodes reac hable | |
| 237 // from this node have also been so marked. We therefore don't n eed | |
| 238 // to traverse the part of the graph below this node during this run | |
| 239 // of the algorithm and so we treat this node as a leaf node. | |
| 240 nodeHolder.state = vsVisitEnded | |
| 241 return | |
| 242 } | |
| 243 | |
| 244 // Mark the visit as started. | |
| 245 nodeHolder.state = vsVisitStarted | |
| 246 | |
| 247 // Next we examine each of the children and recurse into the unvisited o nes. | |
| 248 sawUnverifiedChild := false | |
| 249 for _, child := range nodeHolder.node.OutEdges() { | |
| 250 childHolder := finder.holderForNode(child) | |
| 251 if childHolder.state == vsUnvisited { | |
| 252 // Recursively visit this child. | |
| 253 finder.checkWellFoundedPhase1(childHolder) | |
| 254 } | |
| 255 | |
| 256 // After having visited a child we use the results to update the status of this node. | |
| 257 // We could express the logic here more concisely, but the logi c is easier | |
| 258 // to understand if we treat circles and squares seperately, | |
| 259 if nodeHolder.node.IsSquare() { | |
| 260 if nodeHolder.node.KnownWellFounded() { | |
| 261 // This square node has already been marked |Kno wnWellFounded| becuase | |
| 262 // of an earlier iteration through this loop. Th ere is nothing else to do. | |
| 263 continue | |
| 264 } | |
| 265 if childHolder.node.KnownWellFounded() { | |
| 266 // We mark a square node as |KnownWellFounded| a s soon as we can so | |
| 267 // that if any of its descendants are also paren ts, the well-foundedness | |
| 268 // has a chance to propogate to the descendant i n a recursive call. | |
| 269 nodeHolder.node.SetKnownWellFounded() | |
| 270 } else { | |
| 271 // This square node is not yet known to be well- founded and the child node | |
| 272 // is not yet known to be well-founded. Set up a back link from the child. | |
| 273 childHolder.parentsToBeNotified.Add(nodeHolder) | |
| 274 sawUnverifiedChild = true | |
| 275 } | |
| 276 continue // Done handling the square case. | |
| 277 } | |
| 278 | |
| 279 // Else the node is a circle. If the child is not yet known to b e well-founded | |
| 280 // set up a back link from the child to this node. | |
| 281 if !childHolder.node.KnownWellFounded() { | |
| 282 childHolder.parentsToBeNotified.Add(nodeHolder) | |
| 283 sawUnverifiedChild = true | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 // If a circle node has only well-founded children, or a square node has no children at all, | |
| 288 // then the node is well-founded. | |
| 289 if !sawUnverifiedChild && !nodeHolder.node.KnownWellFounded() { | |
| 290 nodeHolder.node.SetKnownWellFounded() | |
| 291 } | |
| 292 | |
| 293 // Possibly add this node to the |foundationSet| or the |pendingSet|. | |
| 294 if nodeHolder.node.KnownWellFounded() { | |
| 295 if !nodeHolder.parentsToBeNotified.Empty() { | |
| 296 finder.foundationSet.Add(nodeHolder) | |
| 297 } | |
| 298 } else { | |
| 299 finder.pendingSet.Add(nodeHolder) | |
| 300 } | |
| 301 | |
| 302 // Mark the visit as ended. | |
| 303 nodeHolder.state = vsVisitEnded | |
| 304 return | |
| 305 } | |
| 306 | |
| 307 // checkWellFoundedPhase2 performs phase 2 of the algorithm. The goal is to | |
| 308 // propogate known well-foundedness along the back-links that were established | |
| 309 // during phase 1. We have two sets of nodes: the |foundationSet| and the | |
| 310 // |pendingSet|. The |pendingSet| consists of all nodes that are not currently | |
| 311 // known to be well-founded. If the |pendingSet| is not empty when this method | |
| 312 // returns, then the nodes in the |pendingSet| are ill-founded. The algorithm | |
| 313 // proceeds in rounds. The |foundationSet| consists of the current frontier of | |
| 314 // the propogation. That is, the |foundationSet| consists of the nodes discovere d | |
| 315 // to be well-founded in the previous round. (It starts with the nodes discovere d | |
| 316 // to be well-founded during phase 1, pruned to nodes that have parents that | |
| 317 // are in the |pendingSet|.) During each round we propogate known well-foundedne ss | |
| 318 // from the nodes in the |foundationSet| to their parents in the |pendingSet| | |
| 319 // that can now be verified as known well-foudned. | |
| 320 func (finder *cycleFinder) checkWellFoundedPhase2(nodeHolder *nodeHolder) { | |
| 321 for !finder.foundationSet.Empty() { | |
| 322 nextFoundationSet := MakeNodeSet() | |
|
azani
2016/04/22 18:37:14
Alternatively, you could add a Pop() method on Nod
rudominer
2016/04/23 01:22:30
Done.
| |
| 323 for n, _ := range finder.foundationSet.elements { | |
| 324 for p, _ := range n.parentsToBeNotified.elements { | |
| 325 if finder.pendingSet.Contains(p) { | |
| 326 knownWellFounded := true | |
| 327 if !p.node.IsSquare() { | |
| 328 for _, child := range p.node.Out Edges() { | |
| 329 if child != p.node && !c hild.KnownWellFounded() { | |
| 330 knownWellFounded = false | |
| 331 break | |
| 332 } | |
| 333 } | |
| 334 } | |
| 335 if knownWellFounded { | |
| 336 p.node.SetKnownWellFounded() | |
| 337 nextFoundationSet.Add(p) | |
| 338 finder.pendingSet.Remove(p) | |
| 339 } | |
| 340 } | |
| 341 } | |
| 342 } | |
| 343 finder.foundationSet = nextFoundationSet | |
| 344 } | |
| 345 } | |
| 346 | |
| 347 // findKnownCycle finds and returns a |CycleDescription| starting from a node th at is known | |
| 348 // to be ill-founded. This proceeds by following edges from an ill-founded node to | |
| 349 // an ill-founded child node until a cycle is formed. We return a *canonical* cy cle, | |
| 350 // meaning we start from the least possible node and follow edges to the least p ossible | |
|
azani
2016/04/22 18:37:14
"least possible node" is ambiguous.
Also, I wonde
rudominer
2016/04/23 01:22:30
Done
| |
| 351 // child node. This is done in order to make testing of the algorithm easier. | |
| 352 // We are not concerned with optimizing the performance of phase 3 because in th e intended application | |
| 353 // phase 3 can occur at most once in the lifetime of a program: Once an ill-foun ded node is detected the | |
| 354 // program exits with a cycle descritpion allowing the user to fix the ill-found edness. | |
|
azani
2016/04/22 18:37:14
s/descritpion/description
rudominer
2016/04/23 01:22:30
Done.
| |
| 355 func (finder *cycleFinder) findKnownCycle(nodeHolder *nodeHolder) *CycleDescript ion { | |
|
azani
2016/04/22 18:37:14
Maybe rename findKnownIllFoundedCycle.
rudominer
2016/04/23 01:22:30
Done.
| |
| 356 // Mark the current node as started | |
| 357 nodeHolder.state = vsVisitStarted | |
| 358 finder.currentPath.Push(nodeHolder) | |
| 359 for _, child := range nodeHolder.node.OutEdges() { | |
| 360 childHolder := finder.holderForNode(child) | |
| 361 if childHolder.state == vsVisitStarted { | |
| 362 // If the child has been started but not finished then w e have found a cycle | |
| 363 // from the child to the current node back to the child. | |
| 364 return newCycleDescription(finder.currentPath.elements, childHolder, nodeHolder) | |
| 365 } else if !childHolder.node.KnownWellFounded() { | |
| 366 return finder.findKnownCycle(childHolder) | |
| 367 } | |
| 368 } | |
| 369 panic("Should not get here.") | |
|
azani
2016/04/22 18:37:14
Maybe:
"Could not find a known ill-founded cycle.
rudominer
2016/04/23 01:22:30
Done.
| |
| 370 } | |
| 371 | |
| 372 // Returns the nodeHolder for the given node. | |
| 373 func (finder *cycleFinder) holderForNode(node Node) *nodeHolder { | |
| 374 if holder, found := finder.nodeToHolder[node]; found { | |
| 375 return holder | |
| 376 } | |
| 377 | |
| 378 // This is the first time we have seen this node. Assign it a new | |
| 379 // visitor order. | |
| 380 holder := newNodeHolder(node, finder.visitationIndex) | |
| 381 finder.visitationIndex++ | |
| 382 finder.nodeToHolder[node] = holder | |
| 383 return holder | |
| 384 } | |
| 385 | |
| 386 //////////////////////////////////////////////////// | |
| 387 // nodeHolder type | |
| 388 //////////////////////////////////////////////////// | |
| 389 | |
| 390 type visitationState int | |
| 391 | |
| 392 const ( | |
| 393 vsUnvisited visitationState = iota | |
| 394 vsVisitStarted | |
| 395 vsVisitEnded | |
| 396 ) | |
| 397 | |
| 398 // A nodeHolder is an internal data structure used by the algorithm. | |
| 399 // It holds one node plus data about that node used by the algorithm. | |
| 400 type nodeHolder struct { | |
| 401 // The node | |
| 402 node Node | |
| 403 | |
| 404 parentsToBeNotified NodeSet | |
| 405 | |
| 406 visitationOrder int | |
| 407 | |
| 408 state visitationState | |
| 409 } | |
| 410 | |
| 411 func newNodeHolder(node Node, visitationOrder int) *nodeHolder { | |
| 412 nodeHolder := new(nodeHolder) | |
| 413 nodeHolder.node = node | |
| 414 nodeHolder.parentsToBeNotified = MakeNodeSet() | |
| 415 nodeHolder.state = vsUnvisited | |
| 416 nodeHolder.visitationOrder = visitationOrder | |
| 417 return nodeHolder | |
| 418 } | |
| 419 | |
| 420 ////////////////////////////////////////////// | |
| 421 /// nodeStack type | |
| 422 ////////////////////////////////////////////// | |
| 423 | |
| 424 // A nodeStack is a stack of *nodeHolders | |
| 425 type nodeStack struct { | |
| 426 elements []*nodeHolder | |
| 427 } | |
| 428 | |
| 429 func (stack *nodeStack) Push(n *nodeHolder) { | |
| 430 stack.elements = append(stack.elements, n) | |
| 431 } | |
| 432 | |
| 433 func (stack *nodeStack) Size() int { | |
| 434 return len(stack.elements) | |
| 435 } | |
| 436 | |
| 437 func (stack *nodeStack) Pop() (n *nodeHolder) { | |
| 438 lastIndex := stack.Size() - 1 | |
| 439 n = stack.elements[lastIndex] | |
| 440 stack.elements = stack.elements[:lastIndex] | |
| 441 return | |
| 442 } | |
| 443 | |
| 444 func (stack *nodeStack) Peek() (n *nodeHolder) { | |
| 445 return stack.elements[stack.Size()-1] | |
| 446 } | |
| 447 | |
| 448 func (stack *nodeStack) String() string { | |
| 449 var buffer bytes.Buffer | |
| 450 fmt.Fprintf(&buffer, "[") | |
| 451 first := true | |
| 452 for _, e := range stack.elements { | |
| 453 if !first { | |
| 454 fmt.Fprintf(&buffer, ", ") | |
| 455 } | |
| 456 fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | |
| 457 first = false | |
| 458 } | |
| 459 fmt.Fprintln(&buffer, "]") | |
| 460 return buffer.String() | |
| 461 } | |
| 462 | |
| 463 /////////////////////////////////////////////////// | |
| 464 /// NodeSet type | |
| 465 /////////////////////////////////////////////////// | |
| 466 | |
| 467 // A NodeSet is a set of nodeHolders. | |
| 468 type NodeSet struct { | |
| 469 elements map[*nodeHolder]bool | |
| 470 } | |
| 471 | |
| 472 // MakeNodeSet makes a new empty NodeSet. | |
| 473 func MakeNodeSet() NodeSet { | |
| 474 nodeSet := NodeSet{} | |
| 475 nodeSet.elements = make(map[*nodeHolder]bool) | |
| 476 return nodeSet | |
| 477 } | |
| 478 | |
| 479 // Add adds a Node to a NodeSet. | |
| 480 func (set *NodeSet) Add(node *nodeHolder) { | |
| 481 set.elements[node] = true | |
| 482 } | |
| 483 | |
| 484 // AddAll adds all the nodes from |otherSet| to |set|. | |
| 485 func (set *NodeSet) AddAll(otherSet NodeSet) { | |
| 486 for e, _ := range otherSet.elements { | |
| 487 set.elements[e] = true | |
| 488 } | |
| 489 } | |
| 490 | |
| 491 // Contains returns whether or not |node| is an element of |set|. | |
| 492 func (set *NodeSet) Contains(node *nodeHolder) bool { | |
| 493 _, ok := set.elements[node] | |
| 494 return ok | |
| 495 } | |
| 496 | |
| 497 func (set *NodeSet) Remove(node *nodeHolder) { | |
| 498 delete(set.elements, node) | |
| 499 } | |
| 500 | |
| 501 func (set *NodeSet) Empty() bool { | |
| 502 return len(set.elements) == 0 | |
| 503 } | |
| 504 | |
| 505 func (set *NodeSet) ToNodeSlice() []Node { | |
| 506 slice := make([]Node, 0, len(set.elements)) | |
| 507 for n, _ := range set.elements { | |
| 508 slice = append(slice, n.node) | |
| 509 } | |
| 510 return slice | |
| 511 } | |
| 512 | |
| 513 func (set *NodeSet) Size() int { | |
| 514 return len(set.elements) | |
| 515 } | |
| 516 | |
| 517 // compareNodeSets is a package-private method used in our tests. It returns | |
| 518 // a non-nil error in case expected is not equal to actual. | |
| 519 func compareNodeSets(expected, actual *NodeSet) error { | |
| 520 for n, _ := range expected.elements { | |
| 521 if !actual.Contains(n) { | |
| 522 return fmt.Errorf("%s is in expected but not actual", n. node.DebugName()) | |
| 523 } | |
| 524 } | |
| 525 for n, _ := range actual.elements { | |
| 526 if !expected.Contains(n) { | |
| 527 return fmt.Errorf("%s is in actual but not expected", n. node.DebugName()) | |
| 528 } | |
| 529 } | |
| 530 return nil | |
| 531 } | |
| 532 | |
| 533 // String returns a human readable string representation of |set|. | |
| 534 func (set *NodeSet) String() string { | |
| 535 var buffer bytes.Buffer | |
| 536 fmt.Fprintf(&buffer, "{") | |
| 537 first := true | |
| 538 for e, _ := range set.elements { | |
| 539 if !first { | |
| 540 fmt.Fprintf(&buffer, ", ") | |
| 541 } | |
| 542 fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | |
| 543 first = false | |
| 544 } | |
| 545 fmt.Fprintln(&buffer, "}") | |
| 546 return buffer.String() | |
| 547 } | |
| 548 | |
| 549 /////////////////////////////////////////////////// | |
| 550 // CycleDescription type | |
| 551 // | |
| 552 // A |CycleDescription| describes a cycle in a directed graph. | |
| 553 /////////////////////////////////////////////////// | |
| 554 | |
| 555 type CycleDescription struct { | |
| 556 first, last Node | |
| 557 path []Node | |
| 558 } | |
| 559 | |
| 560 func (c *CycleDescription) String() string { | |
| 561 var buffer bytes.Buffer | |
| 562 fmt.Fprintf(&buffer, "first:%s", c.first.DebugName()) | |
| 563 fmt.Fprintf(&buffer, ", last:%s", c.last.DebugName()) | |
| 564 fmt.Fprintf(&buffer, ", path:{") | |
| 565 first := true | |
| 566 for _, n := range c.path { | |
| 567 if !first { | |
| 568 fmt.Fprintf(&buffer, ", ") | |
| 569 } | |
| 570 fmt.Fprintf(&buffer, "%s", n.DebugName()) | |
| 571 first = false | |
| 572 } | |
| 573 fmt.Fprintln(&buffer, "}") | |
| 574 return buffer.String() | |
| 575 } | |
| 576 | |
| 577 func newCycleDescription(path []*nodeHolder, first, last *nodeHolder) *CycleDesc ription { | |
| 578 description := CycleDescription{} | |
| 579 description.first = first.node | |
| 580 description.last = last.node | |
| 581 description.path = make([]Node, 0, len(path)) | |
| 582 for _, n := range path { | |
| 583 if len(description.path) > 0 || n.node == first.node { | |
| 584 description.path = append(description.path, n.node) | |
| 585 } | |
| 586 } | |
| 587 if description.path[len(description.path)-1] != last.node { | |
| 588 panic(fmt.Sprintf("%s != %s", description.path[len(description.p ath)-1], last.node)) | |
| 589 } | |
| 590 return &description | |
| 591 } | |
| OLD | NEW |