Skip to content

Commit d7ec5d0

Browse files
committed
Write rudimentary documentation and update architecture visualization
1 parent 1f32fad commit d7ec5d0

File tree

2 files changed

+154
-3
lines changed

2 files changed

+154
-3
lines changed

Documentation/architecture.png

-12.8 KB
Loading

README.md

+154-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,164 @@
44

55
## What?
66

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).
88

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).
10161

11162
## Architecture
12163

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:
14165

15166
![](Documentation/architecture.png)
16167

0 commit comments

Comments
 (0)