|
4 | 4 |
|
5 | 5 | ## What?
|
6 | 6 |
|
7 |
| -SwiftNodes provides a `Graph` data structure and various graph algorithms. |
| 7 | +SwiftNodes provides a `Graph` data structure and graph algorithms. A `Graph` stores values in nodes which can be connected by edges. SwiftNodes was first used in production by [Codeface](https://www.codeface.io). |
8 | 8 |
|
9 |
| -It serves as critical infrastructure for the [Codeface](https://www.codeface.io) app. |
| 9 | +## How to Edit, Query and Copy Graphs |
| 10 | + |
| 11 | +The following explanations only touch parts of the SwiftNodes API. We recommend exploring the comments, code and unit tests of SwiftNodes. The code in particular is actually very small and easy to grasp. |
| 12 | + |
| 13 | +### Insert Values |
| 14 | + |
| 15 | +A `Graph<NodeID: Hashable, NodeValue>` holds values of type `NodeValue` in nodes. Nodes are unique and have IDs of type `NodeID`: |
| 16 | + |
| 17 | +```swift |
| 18 | +let graph = Graph<String, Int> { "id\($0)" } // NodeID == String, NodeValue == Int |
| 19 | +let node = graph.insert(1) // node.id == "id1", node.value == 1 |
| 20 | + |
| 21 | +let nodeForID1 = graph.node(for: "id1") // nodeForID1 === node |
| 22 | +let valueForID1 = graph.value(for: "id1") // valueForID1 == 1 |
| 23 | +``` |
| 24 | + |
| 25 | +When inserting a value, a `Graph` must know how to generate the ID of the node that would store the value. So the initializer takes a closure returning a `NodeID` given a `NodeValue`. |
| 26 | + |
| 27 | +### Generate Node IDs |
| 28 | + |
| 29 | +You may generate the `NodeID` independent of the `NodeValue`: |
| 30 | + |
| 31 | +```swift |
| 32 | +let graph = Graph<UUID, Int> { _ in UUID() } // NodeID == UUID, NodeValue == Int |
| 33 | +let node1 = graph.insert(42) |
| 34 | +let node2 = graph.insert(42) // node1 !== node2, same value in different nodes |
| 35 | +``` |
| 36 | + |
| 37 | +If `NodeID` and `NodeValue` are the same type, you can omit the closure and the `Graph` will assume the value is itself used as the node ID: |
| 38 | + |
| 39 | +```swift |
| 40 | +let graph = Graph<Int, Int>() // NodeID == NodeValue == Int |
| 41 | +let node1 = graph.insert(42) // node1.value == node1.id == 42 |
| 42 | +let node2 = graph.insert(42) // node1 === node2 because 42 implies the same ID |
| 43 | +``` |
| 44 | + |
| 45 | +And if your `NodeValue` is itself `Identifiable` by IDs of type `NodeID`, then you can also omit the closure and `Graph` will use the `ID` of a `NodeValue` for the node holding that value: |
| 46 | + |
| 47 | +```swift |
| 48 | +struct IdentifiableValue: Identifiable { let id = UUID() } |
| 49 | +let graph = Graph<UUID, IdentifiableValue>() // NodeID == NodeValue.ID == UUID |
| 50 | +let node = graph.insert(IdentifiableValue()) // node.id == node.value.id |
| 51 | +``` |
| 52 | + |
| 53 | +### Connect Nodes via Edges |
| 54 | + |
| 55 | +```swift |
| 56 | +let graph = Graph<String, Int> { "id\($0)" } |
| 57 | +let node1 = graph.insert(1) |
| 58 | +let node2 = graph.insert(2) |
| 59 | +let edge = graph.addEdge(from: node1, |
| 60 | + to: node2) // edge.source === node1, edge.target === node2 |
| 61 | +``` |
| 62 | + |
| 63 | +### Specify Edge Counts |
| 64 | + |
| 65 | +Every `edge` has an integer count accessible via `edge.count`. It is more specifically a "count" rather than a "weight", as it inceases when the same edge is added again. By default, a new edge has count 1 and adding it again increases the count by 1. But you can specify different counts when adding an edge: |
| 66 | + |
| 67 | +```swift |
| 68 | +graph.addEdge(from: node1, to: node2, count: 40) // edge count is 40 |
| 69 | +graph.addEdge(from: node1, to: node2, count: 2) // edge count is 42 |
| 70 | +``` |
| 71 | + |
| 72 | +### Remove Edges |
| 73 | + |
| 74 | +A `GraphEdge<NodeID: Hashable, NodeValue>` has its own `ID` type which combines the `NodeID`s of the edge's source- and target nodes. In the context of a `Graph` or `GraphEdge`, you can create edge IDs easily in two ways: |
| 75 | + |
| 76 | +```swift |
| 77 | +let edgeID_A = Edge.ID(node1, node2) |
| 78 | +let edgeID_B = Edge.ID(node1.id, node2.id) // edgeID_A == edgeID_B |
| 79 | +``` |
| 80 | + |
| 81 | +This leads to six ways of removing an edge: |
| 82 | + |
| 83 | +```swift |
| 84 | +let edge = graph.addEdge(from: node1, to: node2) |
| 85 | + |
| 86 | +graph.remove(edge) |
| 87 | +graph.removeEdge(with: edge.id) |
| 88 | +graph.removeEdge(with: .init(node1, node2)) |
| 89 | +graph.removeEdge(with: .init(node1.id, node2.id)) |
| 90 | +graph.removeEdge(from: node1, to: node2) |
| 91 | +graph.removeEdge(from: node1.id, to: node2.id) |
| 92 | +``` |
| 93 | + |
| 94 | +### Query and Traverse a Graph |
| 95 | + |
| 96 | +`Graph` offers many ways to query its nodes, node IDs, values and edges. Have a look into [Graph.swift](https://github.com/codeface-io/SwiftNodes/blob/master/Code/Graph/Graph.swift) to see them all. In addition, `GraphNode` has caches that enable quick access to its neighbours: |
| 97 | + |
| 98 | +```swift |
| 99 | +node.descendants // all nodes to which there is an edge from node |
| 100 | +node.ancestors // all nodes from which there is an edge to node |
| 101 | +node.neighbours // all descendants and ancestors |
| 102 | +node.isSink // whether node has no descendants |
| 103 | +node.isSource // whether node has no ancestors |
| 104 | +``` |
| 105 | + |
| 106 | +### Sort Nodes |
| 107 | + |
| 108 | +The nodes in a `Graph` maintain an order. So you can also sort them: |
| 109 | + |
| 110 | +```swift |
| 111 | +let graph = Graph<Int, Int>() // NodeID == NodeValue == Int |
| 112 | +graph.insert(5) |
| 113 | +graph.insert(3) // graph.values == [5, 3] |
| 114 | +graph.sort { $0.id < $1.id } // graph.values == [3, 5] |
| 115 | +``` |
| 116 | + |
| 117 | +### Copy a Graph |
| 118 | + |
| 119 | +Many algorithms produce a variant of a given graph. Rather than modifying the original graph, SwiftNodes suggests to make a copy. |
| 120 | + |
| 121 | +A `graph.copy()` is identical to the original `graph` in IDs, values and structure but contains its own new node- and edge objects. You may also copy just a subset of a graph and limit the included edges and/or nodes: |
| 122 | + |
| 123 | +```swift |
| 124 | +let subsetCopy = graph.copy(includedNodes: [node2, node3], |
| 125 | + includedEdges: [edge23]) |
| 126 | +``` |
| 127 | + |
| 128 | +## How to Write Graph Algorithms |
| 129 | + |
| 130 | +To support algorithms, every `node` has an optional property `node.marking` which can store a `GraphNode.Marking` object. A marking can be used to generally mark a node, but it also has two integers and two boolean flags that algorithms can use in whatever way they need. |
| 131 | + |
| 132 | +[Graph+Node.Marking.swift](https://github.com/codeface-io/SwiftNodes/blob/master/Code/Graph%2BAlgorithms/Graph%2BNode.Marking.swift) contains some conveniences for marking and unmarking nodes. Also have a look at how the [included algorithms](https://github.com/codeface-io/SwiftNodes/tree/master/Code/Graph%2BAlgorithms) make use of node markings. |
| 133 | + |
| 134 | +## Included Algorithms |
| 135 | + |
| 136 | +SwiftNodes has begun to accumulate [some graph algorithms](https://github.com/codeface-io/SwiftNodes/tree/master/Code/Graph%2BAlgorithms). The following overview also links to Wikipedia articles that explain what the algorithms do. We recommend also exploring them in code. |
| 137 | + |
| 138 | +### Components |
| 139 | + |
| 140 | +`graph.findComponents()` returns multiple sets of nodes which represent the [components](https://en.wikipedia.org/wiki/Component_(graph_theory)) of the `graph`. |
| 141 | + |
| 142 | +### Strongly Connected Components |
| 143 | + |
| 144 | +`graph.findStronglyConnectedComponents()` returns multiple sets of nodes which represent the [strongly connected components](https://en.wikipedia.org/wiki/Strongly_connected_component) of the `graph`. |
| 145 | + |
| 146 | +### Condensation Graph |
| 147 | + |
| 148 | +`graph.makeCondensationGraph()` creates the [condensation graph](https://en.wikipedia.org/wiki/Strongly_connected_component) of the `graph`, which is the graph in which all [strongly connected components](https://en.wikipedia.org/wiki/Strongly_connected_component) of the original graph have been collapsed into single nodes, so the resulting condensation graph is acyclic. |
| 149 | + |
| 150 | +### Minimum Equivalent Graph |
| 151 | + |
| 152 | +`graph.makeMinimumEquivalentGraph()` creates the [MEG](https://en.wikipedia.org/wiki/Transitive_reduction) of the `graph`. This only works on acyclic graphs and might even hang or crash on cyclic ones. |
| 153 | + |
| 154 | +### Ancestor Counts |
| 155 | + |
| 156 | +`graph.findNumberOfNodeAncestors()` returns a `[(Node, Int)]` containing each node of the graph associated with its ancestor count. The ancestor count is the number of all (recursive) ancestors of the node. Basically, it's the number of other nodes from which the node can be reached. |
| 157 | + |
| 158 | +This only works on acyclic graphs right now and might return incorrect results for some nodes in cyclic graphs. |
| 159 | + |
| 160 | +Ancestor counts can serve as a proxy for [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting). |
10 | 161 |
|
11 | 162 | ## Architecture
|
12 | 163 |
|
13 |
| -Here is the internal architecture (composition and essential dependencies): |
| 164 | +Here is the internal architecture (composition and essential dependencies) of the SwiftNodes code folder: |
14 | 165 |
|
15 | 166 | 
|
16 | 167 |
|
|
0 commit comments