Skip to content

Commit 249e98e

Browse files
committed
Merge branch 'release/0.10.0'
2 parents 7e7ebfb + f89e06a commit 249e98e

6 files changed

Lines changed: 344 additions & 8 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,29 @@ Contributions are welcome. Please submit a pull request to the develop branch in
9191

9292
# Releases
9393

94+
## 0.10.0
95+
96+
This release features breadth first search methods. These are methods that follow the standard collection methods
97+
that come with groovy: eachBfs, findBfs, findAllBfs, injectBfs, and collectBfs. There are two overrides for each.
98+
The first takes a closure and starts at the first vertex in the vertices map. The second can specify the root to
99+
start at in the breadth first traversal. Here is a short example using findBfs:
100+
101+
```groovy
102+
findBfs {
103+
it.work > 0
104+
}
105+
```
106+
107+
Finds the first vertex with `work > 0`
108+
109+
```groovy
110+
findBfs('step4') {
111+
it.work > 0
112+
}
113+
```
114+
115+
Finds the first vertex with `work > 0` starting at step4.
116+
94117
## 0.9.0
95118

96119
Added reversePostOrder to DirectedGraphPlugin. This will allow a DirectedGraph to perform a

src/main/groovy/graph/DirectedGraphPlugin.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class DirectedGraphPlugin implements Plugin {
2828
graph.metaClass.inDegree = this.&inDegree.curry(graph)
2929
graph.metaClass.outEdges = this.&outEdges.curry(graph)
3030
graph.metaClass.outDegree = this.&outDegree.curry(graph)
31-
graph.metaClass.adjacentEdges = this.&adjacentEdges.curry(graph)
31+
graph.metaClass.traverseEdges = this.&traverseEdges.curry(graph)
3232
graph.metaClass.reversePostOrderSort = this.&reversePostOrderSort.curry(graph)
3333
graph.metaClass.reversePostOrder = this.&reversePostOrder.curry(graph)
3434
}
@@ -83,7 +83,7 @@ class DirectedGraphPlugin implements Plugin {
8383
* @param name
8484
* @return
8585
*/
86-
static Set<? extends Edge> adjacentEdges(Graph graph, String name) {
86+
static Set<? extends Edge> traverseEdges(Graph graph, String name) {
8787
graph.outEdges(name)
8888
}
8989

src/main/groovy/graph/Graph.groovy

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ class Graph {
384384
* @return the name of the first unvisited child vertex
385385
*/
386386
def getUnvisitedChildName(colors, parentName) {
387-
def edge = adjacentEdges(parentName).findAll {
387+
def edge = traverseEdges(parentName).findAll {
388388
it.one != it.two
389389
}.find {
390390
def childName = parentName == it.one ? it.two : it.one
@@ -403,12 +403,21 @@ class Graph {
403403
* @param name
404404
* @return set of adjacent edges.
405405
*/
406-
Set<? extends Edge> adjacentEdges(name) {
406+
Set<? extends Edge> adjacentEdges(String name) {
407407
edges.findAll {
408408
name == it.one || name == it.two
409409
}
410410
}
411411

412+
/**
413+
* Returns edges from vertex with name that should be traversed.
414+
* @param name
415+
* @return
416+
*/
417+
Set<? extends Edge> traverseEdges(String name) {
418+
adjacentEdges(name)
419+
}
420+
412421
/**
413422
* creates and returns a color map in the form of
414423
* name : color. name is the vertex
@@ -532,7 +541,7 @@ class Graph {
532541
}
533542
spec.colors[root] = TraversalColor.GREY
534543

535-
Set<Edge> adjacentEdges = adjacentEdges(root)
544+
Set<Edge> adjacentEdges = traverseEdges(root)
536545
for (int index = 0; index < adjacentEdges.size(); index++) { //cannot stop and each() call on adjacentEdges
537546
Edge edge = adjacentEdges[index]
538547
String connectedName = root == edge.one ? edge.two : edge.one
@@ -555,6 +564,135 @@ class Graph {
555564
null
556565
}
557566

567+
/**
568+
* executes closure on each {@link Vertex} in breadth first order. See {@link #breadthFirstTraversal} for details.
569+
* @param closure
570+
*/
571+
void eachBfs(Closure closure) {
572+
eachBfs(null, closure)
573+
}
574+
575+
/**
576+
* executes closure on each {@link Vertex} in breadth first order starting at the given root {@link Vertex}. See {@link #breadthFirstTraversal} for details.
577+
* @param root
578+
* @param closure
579+
*/
580+
void eachBfs(String root, Closure closure) {
581+
breadthFirstTraversal {
582+
delegate.root = root
583+
visit { vertex ->
584+
closure(vertex)
585+
return null
586+
}
587+
}
588+
}
589+
590+
/**
591+
* Executes closure on each {@link Vertex} in breadth first order. If the closure returns true the {@link Vertex} is
592+
* returned.
593+
* @param closure closure to execute on each {@link Vertex}
594+
* @return first {@link Vertex} where closure returns true
595+
*/
596+
Vertex findBfs(Closure closure) {
597+
findBfs(null, closure)
598+
}
599+
600+
/**
601+
* Executes closure on each {@link Vertex} in breadth first order starting at root. If the closure returns true the
602+
* {@link Vertex} is returned.
603+
* @param root where to start breadth first traversal
604+
* @param closure closure to execute on each {@link Vertex}
605+
* @return first {@link Vertex} where closure returns true
606+
*/
607+
Vertex findBfs(String root, Closure closure) {
608+
Vertex result = null
609+
breadthFirstTraversal {
610+
delegate.root = root
611+
visit { vertex ->
612+
if (closure(vertex)) {
613+
result = vertex
614+
return Traversal.STOP
615+
}
616+
}
617+
}
618+
result
619+
}
620+
621+
/**
622+
* Executes closure on each vertex in breadth first order. object is the initial value passed to the closure. Each returned
623+
* value from the closure is passed to the next call.
624+
* @param object
625+
* @param closure
626+
* @return object returned from the final call to closure.
627+
*/
628+
def injectBfs(Object object, Closure closure) {
629+
injectBfs(null, object, closure)
630+
}
631+
632+
/**
633+
* Executes closure on each vertex in breadth first order starting at root. object is the initial value passed to the closure. Each returned
634+
* value from the closure is passed to the next call.
635+
* @param root
636+
* @param object
637+
* @param closure
638+
* @return object returned from the final call to closure.
639+
*/
640+
def injectBfs(String root, Object object, Closure closure) {
641+
Object result = object
642+
breadthFirstTraversal {
643+
delegate.root = root
644+
visit { vertex ->
645+
result = closure(result, vertex)
646+
}
647+
}
648+
result
649+
}
650+
651+
/**
652+
* Runs closure on each vertex in breadth first order. The vertices where closure returns true are returned.
653+
* @param closure to run on each vertex
654+
* @return the vertices where closure returns true
655+
*/
656+
def findAllBfs(Closure closure) {
657+
findAllBfs(null, closure)
658+
}
659+
660+
/**
661+
* Runs closure on each vertex in breadth first order starting at root. The vertices where closure returns true are returned.
662+
* @param root the vertex to start from
663+
* @param closure to run on each vertex
664+
* @return the vertices where closure returns true
665+
*/
666+
def findAllBfs(String root, Closure closure) {
667+
injectBfs(root, []) { result, vertex ->
668+
if(closure(vertex)) {
669+
result << vertex
670+
}
671+
result
672+
}
673+
}
674+
675+
/**
676+
* Runs closure on each vertex in breadth first order collecting the result.
677+
* @param closure to run on each vertex
678+
* @return the results from closure
679+
*/
680+
def collectBfs(Closure closure) {
681+
collectBfs(null, closure)
682+
}
683+
684+
/**
685+
* Runs closure on each vertex in breadth first order, starting at root, collecting the result.
686+
* @param root vertex to start at
687+
* @param closure to run on each vertex
688+
* @return the results from closure
689+
*/
690+
def collectBfs(String root, Closure closure) {
691+
injectBfs(root, []) { result, vertex ->
692+
result << closure(vertex)
693+
}
694+
}
695+
558696
/**
559697
* configures a breadth first traversal with the given closure using
560698
* breadthFirstTraversalSpec().
@@ -595,7 +733,7 @@ class Graph {
595733
queue << root
596734
while (queue.size() != 0) {
597735
String current = queue.poll()
598-
Set<Edge> adjacentEdges = adjacentEdges(current)
736+
Set<Edge> adjacentEdges = traverseEdges(current)
599737
for (int i = 0; i < adjacentEdges.size(); i++) {
600738
Edge edge = adjacentEdges[i]
601739
String connected = current == edge.one ? edge.two : edge.one
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package graph
2+
3+
import spock.lang.Specification
4+
5+
class BreadthFirstSearchMethods extends Specification {
6+
7+
Graph graph = new Graph()
8+
def defaultOrder = []
9+
def orderFromD = []
10+
11+
def setup() {
12+
graph.with {
13+
vertex 'A', [connectsTo:['B', 'D']]
14+
vertex 'B', [connectsTo:['C', 'D']]
15+
vertex 'D', [connectsTo:['C', 'E', 'A']]
16+
}
17+
graph.breadthFirstTraversal {
18+
visit { vertex ->
19+
defaultOrder << vertex.name
20+
}
21+
}
22+
graph.breadthFirstTraversal {
23+
root = 'D'
24+
visit { vertex ->
25+
orderFromD << vertex.name
26+
}
27+
}
28+
}
29+
30+
def 'eachBfs is in breadthFirstOrder'() {
31+
when:
32+
def result = []
33+
graph.eachBfs {
34+
result << it.name
35+
}
36+
37+
then:
38+
defaultOrder == result
39+
}
40+
41+
def 'eachBfs can start at different root'() {
42+
when:
43+
def result = []
44+
graph.eachBfs('D') {
45+
result << it.name
46+
}
47+
48+
then:
49+
orderFromD == result
50+
}
51+
52+
def 'findBfs is in breadth first order'() {
53+
when:
54+
def result = []
55+
graph.findBfs {
56+
result << it.name
57+
false
58+
}
59+
60+
then:
61+
defaultOrder == result
62+
}
63+
64+
def 'findBfs can start at different root'() {
65+
when:
66+
def result = []
67+
graph.findBfs('D') {
68+
result << it.name
69+
false
70+
}
71+
72+
then:
73+
orderFromD == result
74+
}
75+
76+
def 'findBfs can find vertex'() {
77+
when:
78+
Vertex vertex = graph.findBfs {
79+
it.name == 'D'
80+
}
81+
82+
then:
83+
vertex.name == 'D'
84+
}
85+
86+
def 'injectBfs is in breadth first order'() {
87+
when:
88+
def result = []
89+
graph.injectBfs(result) { list, vertex ->
90+
list << vertex.name
91+
list
92+
}
93+
94+
then:
95+
defaultOrder == result
96+
}
97+
98+
def 'injectBfs can start at different root'() {
99+
when:
100+
def result = []
101+
graph.injectBfs('D', result) { list, vertex ->
102+
list << vertex.name
103+
list
104+
}
105+
106+
then:
107+
orderFromD == result
108+
}
109+
110+
def 'findAllBfs is in breadth first order'() {
111+
when:
112+
def result = graph.findAllBfs{ true }
113+
114+
then:
115+
defaultOrder == result*.name
116+
}
117+
118+
def 'findAllBfs can start at different root'() {
119+
when:
120+
def result = graph.findAllBfs('D') { true }
121+
122+
then:
123+
orderFromD == result*.name
124+
}
125+
126+
def 'findAllBfs can find all'() {
127+
when:
128+
def result = graph.findAllBfs {
129+
it.name in ['A', 'C', 'E']
130+
}
131+
132+
then:
133+
result*.name == ['A', 'C', 'E']
134+
}
135+
136+
def 'collectBfs is in breadth first order'() {
137+
when:
138+
def result = graph.collectBfs { it }
139+
140+
then:
141+
defaultOrder == result*.name
142+
}
143+
144+
def 'collectBfs can start at different root'() {
145+
when:
146+
def result = graph.collectBfs('D') { it }
147+
148+
then:
149+
orderFromD == result*.name
150+
}
151+
152+
def 'collectBfs can get names'() {
153+
when:
154+
def result = graph.collectBfs { it.name }
155+
156+
then:
157+
defaultOrder == result
158+
}
159+
}

0 commit comments

Comments
 (0)