Skip to content

Commit 80cb252

Browse files
committed
Avoid requiring node- or edge values (don't imply reference semantics)
1 parent 4f1b951 commit 80cb252

File tree

6 files changed

+50
-99
lines changed

6 files changed

+50
-99
lines changed

Code/Graph+Algorithms/Graph+AncestorCount.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public extension Graph
3232
ancestorCountByNode[node] = 0 // mark node as visited to avoid infinite loops in cyclic graphs
3333

3434
let directAncestors = node.ancestorIDs.compactMap { self.node(for: $0) }
35-
let ingoingEdges = directAncestors.compactMap { edge(from: $0, to: node) }
35+
let ingoingEdges = directAncestors.compactMap { edge(from: $0.id, to: node.id) }
3636
let directAncestorCount = ingoingEdges.sum { $0.count }
3737

3838
let ancestorCount = directAncestorCount + directAncestors.sum

Code/Graph+Algorithms/Graph+MinimumEquivalentGraph.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public extension Graph
2424
}
2525

2626
var minimumEquivalentGraph = self
27-
nonEssentialEdges.forEach { minimumEquivalentGraph.remove($0) }
27+
nonEssentialEdges.forEach { minimumEquivalentGraph.removeEdge(with: $0.id) }
2828
return minimumEquivalentGraph
2929
}
3030

@@ -53,7 +53,7 @@ public extension Graph
5353
{
5454
for ancestor in ancestorsToConsider
5555
{
56-
if let nonEssentialEdge = edge(from: ancestor, to: descendant)
56+
if let nonEssentialEdge = edge(from: ancestor.id, to: descendant.id)
5757
{
5858
nonEssentialEdges += nonEssentialEdge
5959
}

Code/Graph/Graph+SubGraph.swift

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import OrderedCollections
22

33
public extension Graph
44
{
5-
func subGraph(nodes: OrderedSet<Node>) -> Graph where NodeValue: Identifiable, NodeValue.ID == NodeID
5+
func subGraph(nodeIDs: Set<NodeID>) -> Graph
66
{
7-
let nodeIDs = Set(nodes.map { $0.id })
8-
9-
var subGraph = Graph(values: nodes.map { $0.value })
7+
var subGraph = Graph(values: nodeIDs.compactMap { nodesByID[$0]?.value },
8+
makeNodeIDForValue: makeNodeIDForValue)
109

1110
for edge in edges
1211
{

Code/Graph/Graph.swift

+21-58
Original file line numberDiff line numberDiff line change
@@ -13,85 +13,64 @@ public struct Graph<NodeID, NodeValue>: Sendable
1313
{
1414
// MARK: - Initialize
1515

16-
public init(values: [NodeValue]) where NodeValue: Identifiable, NodeValue.ID == NodeID
17-
{
18-
let nodeArray = values.map { GraphNode(id: $0.id, value: $0) }
19-
self.init(nodes: OrderedSet(nodeArray))
20-
}
21-
22-
// TODO: nowhere should the client be required to provide nodes, since nodes contain edge caches. only the graph should create nodes. the client only reads them.
23-
2416
/**
2517
Uses the `NodeValue.ID` of a value as the ``GraphNode/id`` for its corresponding node
2618
*/
27-
public init(nodes: OrderedNodes = []) where NodeValue: Identifiable, NodeValue.ID == NodeID
19+
public init(values: [NodeValue] = []) where NodeValue: Identifiable, NodeValue.ID == NodeID
2820
{
29-
self.init(nodes: nodes) { $0.id }
21+
self.init(values: values) { $0.id }
3022
}
3123

3224
/**
3325
Uses a `NodeValue` itself as the ``GraphNode/id`` for its corresponding node
3426
*/
35-
public init(nodes: OrderedNodes = []) where NodeID == NodeValue
27+
public init(values: [NodeValue] = []) where NodeID == NodeValue
3628
{
37-
self.init(nodes: nodes) { $0 }
29+
self.init(values: values) { $0 }
3830
}
3931

4032
/**
4133
Creates a `Graph` that generates ``GraphNode/id``s for new ``GraphNode``s with the given closure
4234
*/
43-
public init(nodes: OrderedNodes = [],
35+
public init(values: [NodeValue] = [],
4436
makeNodeIDForValue: @Sendable @escaping (NodeValue) -> NodeID)
4537
{
46-
nodesByID = .init(uniqueKeysWithValues: nodes.map { ($0.id, $0) })
38+
let idsWithNodes = values.map
39+
{
40+
let id = makeNodeIDForValue($0)
41+
return (id, Node(id: id, value: $0))
42+
}
43+
44+
nodesByID = .init(uniqueKeysWithValues: idsWithNodes)
4745
self.makeNodeIDForValue = makeNodeIDForValue
4846
}
4947

5048
// MARK: - Edges
5149

5250
/**
53-
Removes the corresponding ``GraphEdge``, see ``Graph/remove(_:)``
51+
Removes the corresponding ``GraphEdge``, see ``Graph/removeEdge(with:)``
5452
*/
55-
public mutating func removeEdge(from origin: Node, to destination: Node)
56-
{
57-
removeEdge(from: origin.id, to: destination.id)
58-
}
59-
60-
/**
61-
Removes the corresponding ``GraphEdge``, see ``Graph/remove(_:)``
62-
*/
63-
public mutating func removeEdge(from originID: NodeID, to destinationID: NodeID)
53+
@discardableResult
54+
public mutating func removeEdge(from originID: NodeID,
55+
to destinationID: NodeID) -> Edge?
6456
{
6557
removeEdge(with: .init(originID, destinationID))
6658
}
6759

68-
/**
69-
Removes the corresponding ``GraphEdge``, see ``Graph/remove(_:)``
70-
*/
71-
public mutating func remove(_ edge: Edge)
72-
{
73-
removeEdge(with: edge.id)
74-
}
75-
7660
/**
7761
Removes the ``GraphEdge`` with the given ID, also removing it from the caches of its ``GraphEdge/origin`` and ``GraphEdge/destination``
7862
*/
79-
public mutating func removeEdge(with edgeID: Edge.ID)
63+
@discardableResult
64+
public mutating func removeEdge(with edgeID: Edge.ID) -> Edge?
8065
{
8166
// remove from node caches
8267
nodesByID[edgeID.originID]?.descendantIDs -= edgeID.destinationID
8368
nodesByID[edgeID.destinationID]?.ancestorIDs -= edgeID.originID
8469

8570
// remove edge itself
71+
let edge = edgesByID[edgeID]
8672
edgesByID[edgeID] = nil
87-
}
88-
89-
@discardableResult
90-
public mutating func addEdge(from origin: Node,
91-
to destination: Node,
92-
count: Int = 1) -> Edge
93-
{
94-
addEdge(from: origin.id, to: destination.id, count: count)
73+
return edge
9574
}
9675

9776
/**
@@ -130,14 +109,6 @@ public struct Graph<NodeID, NodeValue>: Sendable
130109
}
131110
}
132111

133-
/**
134-
The ``GraphEdge`` from `origin` to `destination` if it exists, otherwise `nil`
135-
*/
136-
public func edge(from origin: Node, to destination: Node) -> Edge?
137-
{
138-
edge(from: origin.id, to: destination.id)
139-
}
140-
141112
/**
142113
The ``GraphEdge`` between the corresponding nodes if it exists, otherwise `nil`
143114
*/
@@ -182,7 +153,7 @@ public struct Graph<NodeID, NodeValue>: Sendable
182153
return node
183154
}
184155

185-
internal let makeNodeIDForValue: @Sendable (NodeValue) -> NodeID
156+
public let makeNodeIDForValue: @Sendable (NodeValue) -> NodeID
186157

187158
/**
188159
``GraphNode/value`` of the ``GraphNode`` with the given ``GraphNode/id`` if one exists, otherwise `nil`
@@ -218,14 +189,6 @@ public struct Graph<NodeID, NodeValue>: Sendable
218189
nodesByID.values.filter { $0.isSink }
219190
}
220191

221-
/**
222-
Whether the `Graph` contains the given ``GraphNode``
223-
*/
224-
public func contains(_ node: Node) -> Bool
225-
{
226-
contains(node.id)
227-
}
228-
229192
/**
230193
Whether the `Graph` contains a ``GraphNode`` with the given ``GraphNode/id``
231194
*/

Code/Graph/GraphEdge.swift

+1-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@ public struct GraphEdge<NodeID: Hashable, NodeValue>: Identifiable, Hashable, Se
4444
An edge's `ID` combines the ``GraphNode/id``s of its ``GraphEdge/origin`` and ``GraphEdge/destination``
4545
*/
4646
public struct ID: Hashable, Sendable
47-
{
48-
internal init(_ origin: Node, _ destination: Node)
49-
{
50-
self.init(origin.id, destination.id)
51-
}
52-
47+
{
5348
internal init(_ originID: NodeID, _ destinationID: NodeID)
5449
{
5550
self.originID = originID

Tests/SwiftNodesTests.swift

+22-28
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,31 @@ class SwiftNodesTests: XCTestCase {
1818
var graph = Graph<String, Int> { "id\($0)" }
1919
let node1 = graph.insert(1)
2020
let node2 = graph.insert(2)
21-
graph.addEdge(from: node1, to: node2)
21+
graph.addEdge(from: node1.id, to: node2.id)
2222

23-
XCTAssertNotNil(graph.edge(from: node1, to: node2))
24-
XCTAssertNil(graph.edge(from: node2, to: node1))
25-
XCTAssertEqual(graph.edge(from: node1, to: node2)?.count, 1)
23+
XCTAssertNotNil(graph.edge(from: node1.id, to: node2.id))
24+
XCTAssertNil(graph.edge(from: node2.id, to: node1.id))
25+
XCTAssertEqual(graph.edge(from: node1.id, to: node2.id)?.count, 1)
2626
}
2727

2828
func testAddingEdgeWithBigCount() throws {
2929
var graph = Graph<String, Int> { "id\($0)" }
3030
let node1 = graph.insert(1)
3131
let node2 = graph.insert(2)
3232

33-
graph.addEdge(from: node1, to: node2, count: 42)
34-
XCTAssertEqual(graph.edge(from: node1, to: node2)?.count, 42)
33+
graph.addEdge(from: node1.id, to: node2.id, count: 42)
34+
XCTAssertEqual(graph.edge(from: node1.id, to: node2.id)?.count, 42)
3535

36-
graph.addEdge(from: node1, to: node2, count: 58)
37-
XCTAssertEqual(graph.edge(from: node1, to: node2)?.count, 100)
36+
graph.addEdge(from: node1.id, to: node2.id, count: 58)
37+
XCTAssertEqual(graph.edge(from: node1.id, to: node2.id)?.count, 100)
3838
}
3939

4040
func testEdgesAreDirected() throws {
4141
var graph = Graph<String, Int> { "id\($0)" }
4242
let node1 = graph.insert(1)
4343
let node2 = graph.insert(2)
44-
XCTAssertNotNil(graph.addEdge(from: node1, to: node2))
45-
XCTAssertNil(graph.edge(from: node2, to: node1))
44+
XCTAssertNotNil(graph.addEdge(from: node1.id, to: node2.id))
45+
XCTAssertNil(graph.edge(from: node2.id, to: node1.id))
4646
}
4747

4848
func testUUIDAsID() throws {
@@ -66,33 +66,27 @@ class SwiftNodesTests: XCTestCase {
6666

6767
let node1 = graph.insert(1)
6868
let node2 = graph.insert(2)
69-
let edge = graph.addEdge(from: node1, to: node2)
69+
let edge = graph.addEdge(from: node1.id, to: node2.id)
7070

71-
graph.remove(edge)
7271
graph.removeEdge(with: edge.id)
73-
graph.removeEdge(with: .init(node1, node2))
7472
graph.removeEdge(with: .init(node1.id, node2.id))
75-
graph.removeEdge(from: node1, to: node2)
7673
graph.removeEdge(from: node1.id, to: node2.id)
7774
}
7875

7976
func testSixWaysToRemoveAnEdgeDoCompile() {
8077
var graph = Graph<Int, Int>()
8178
let node1 = graph.insert(5)
8279
let node2 = graph.insert(3)
83-
let edge = graph.addEdge(from: node1, to: node2)
80+
let edge = graph.addEdge(from: node1.id, to: node2.id)
8481

8582
XCTAssertNotNil(edge)
86-
XCTAssertEqual(edge.id, graph.edge(from: node1, to: node2)?.id)
83+
XCTAssertEqual(edge.id, graph.edge(from: node1.id, to: node2.id)?.id)
8784

88-
graph.remove(edge)
85+
graph.removeEdge(with: edge.id)
8986

90-
XCTAssertNil(graph.edge(from: node1, to: node2))
87+
XCTAssertNil(graph.edge(from: node1.id, to: node2.id))
9188

92-
graph.removeEdge(with: edge.id)
93-
graph.removeEdge(with: .init(node1, node2))
9489
graph.removeEdge(with: .init(node1.id, node2.id))
95-
graph.removeEdge(from: node1, to: node2)
9690
graph.removeEdge(from: node1.id, to: node2.id)
9791
}
9892

@@ -112,15 +106,15 @@ class SwiftNodesTests: XCTestCase {
112106

113107
let node2 = graph.insert(2)
114108
XCTAssertNil(graph.edge(from: "id1", to: "id2"))
115-
XCTAssertNil(graph.edge(from: node1, to: node2))
109+
XCTAssertNil(graph.edge(from: node1.id, to: node2.id))
116110

117111
let edge12 = graph.addEdge(from: node1.id, to: node2.id)
118112
XCTAssertNotNil(graph.edge(from: "id1", to: "id2"))
119-
XCTAssertNotNil(graph.edge(from: node1, to: node2))
113+
XCTAssertNotNil(graph.edge(from: node1.id, to: node2.id))
120114

121115
XCTAssertEqual(edge12.count, 1)
122116
XCTAssertEqual(edge12.id, graph.addEdge(from: "id1", to: "id2").id)
123-
XCTAssertEqual(graph.edge(from: node1, to: node2)?.count, 2)
117+
XCTAssertEqual(graph.edge(from: node1.id, to: node2.id)?.count, 2)
124118
XCTAssertEqual(edge12.originID, node1.id)
125119
XCTAssertEqual(edge12.destinationID, node2.id)
126120

@@ -144,7 +138,7 @@ class SwiftNodesTests: XCTestCase {
144138

145139
graph.removeEdge(with: edge12.id)
146140
XCTAssertNil(graph.edge(from: "id1", to: "id2"))
147-
XCTAssertNil(graph.edge(from: node1, to: node2))
141+
XCTAssertNil(graph.edge(from: node1.id, to: node2.id))
148142

149143
guard let finalNode1 = graph.node(for: node1.id),
150144
let finalNode2 = graph.node(for: node2.id) else
@@ -170,9 +164,9 @@ class SwiftNodesTests: XCTestCase {
170164
let node2 = graph.insert(2)
171165
let node3 = graph.insert(3)
172166

173-
graph.addEdge(from: node1, to: node2)
174-
graph.addEdge(from: node2, to: node3)
175-
graph.addEdge(from: node1, to: node3)
167+
graph.addEdge(from: node1.id, to: node2.id)
168+
graph.addEdge(from: node2.id, to: node3.id)
169+
graph.addEdge(from: node1.id, to: node3.id)
176170

177171
XCTAssertEqual(graph.edges.count, 3)
178172

0 commit comments

Comments
 (0)