@@ -28,6 +28,10 @@ private func topologicalSort(_ node: Int, _ successors: [Int: [Int]]) throws ->
28
28
return try topologicalSort ( [ node] , successors)
29
29
}
30
30
31
+ private func findCycle( _ node: Int , _ successors: [ Int : [ Int ] ] ) -> ( path: [ Int ] , cycle: [ Int ] ) ? {
32
+ return findCycle ( [ node] , successors: { successors [ $0] ?? [ ] } )
33
+ }
34
+
31
35
class GraphAlgorithmsTests : XCTestCase {
32
36
func testTransitiveClosure( ) {
33
37
// A trival graph.
@@ -70,8 +74,39 @@ class GraphAlgorithmsTests: XCTestCase {
70
74
XCTAssertThrows ( GraphError . unexpectedCycle) { _ = try topologicalSort ( 1 , [ 1 : [ 2 ] , 2 : [ 1 ] ] ) }
71
75
}
72
76
77
+ func testCycleDetection( ) throws {
78
+ // Single node graph.
79
+ XCTAssertNotCycle ( findCycle ( 1 , [ : ] ) )
80
+ XCTAssertNotCycle ( findCycle ( 1 , [ 1 : [ 2 ] ] ) )
81
+ // Trivial cycles.
82
+ XCTAssertCycle ( findCycle ( 1 , [ 1 : [ 1 ] ] ) , path: [ ] , cycle: [ 1 ] )
83
+ XCTAssertCycle ( findCycle ( 1 , [ 1 : [ 2 ] , 2 : [ 1 ] ] ) , path: [ ] , cycle: [ 1 , 2 ] )
84
+ XCTAssertCycle ( findCycle ( 1 , [ 1 : [ 2 ] , 2 : [ 3 ] , 3 : [ 2 ] ] ) , path: [ 1 ] , cycle: [ 2 , 3 ] )
85
+ XCTAssertCycle ( findCycle ( 1 , [ 1 : [ 2 ] , 2 : [ 3 ] , 3 : [ 1 ] ] ) , path: [ ] , cycle: [ 1 , 2 , 3 ] )
86
+
87
+ XCTAssertNotCycle ( findCycle ( 1 , [ 1 : [ 2 , 3 ] , 2 : [ 3 , 4 ] , 3 : [ 4 , 5 ] , 4 : [ 5 , 8 ] , 5 : [ 7 , 8 ] , 7 : [ 8 ] ] ) )
88
+ XCTAssertCycle ( findCycle ( 1 , [ 1 : [ 2 ] , 2 : [ 3 , 4 ] , 3 : [ 4 , 5 ] , 4 : [ 1 , 5 , 8 ] , 5 : [ 7 , 8 ] , 7 : [ 8 ] ] ) , path: [ ] , cycle: [ 1 , 2 , 3 , 4 ] )
89
+
90
+ XCTAssertNotCycle ( findCycle ( 1 , [ 1 : [ 2 , 3 ] , 2 : [ ] , 3 : [ 2 ] ] ) )
91
+ }
92
+
73
93
static var allTests = [
94
+ ( " testCycleDetection " , testCycleDetection) ,
74
95
( " testTransitiveClosure " , testTransitiveClosure) ,
75
96
( " testTopologicalSort " , testTopologicalSort) ,
76
97
]
77
98
}
99
+
100
+ private func XCTAssertCycle< T: Equatable > ( _ cycleResult: ( path: [ T ] , cycle: [ T ] ) ? , path: [ T ] , cycle: [ T ] , file: StaticString = #file, line: UInt = #line) {
101
+ guard let cycleResult = cycleResult else {
102
+ return XCTFail ( " Expected cycle but not found " , file: file, line: line)
103
+ }
104
+ XCTAssertEqual ( cycleResult. path, path, file: file, line: line)
105
+ XCTAssertEqual ( cycleResult. cycle, cycle, file: file, line: line)
106
+ }
107
+
108
+ private func XCTAssertNotCycle< T> ( _ cycleResult: T ? , file: StaticString = #file, line: UInt = #line) {
109
+ if let cycleResult = cycleResult {
110
+ XCTFail ( " Unexpected cycle found \( cycleResult) " , file: file, line: line)
111
+ }
112
+ }
0 commit comments