Skip to content

Commit 6dd71e7

Browse files
committed
Test and fix minimum equivalent graph: Edge is a reference type!
1 parent 60bb6f5 commit 6dd71e7

File tree

4 files changed

+55
-19
lines changed

4 files changed

+55
-19
lines changed

Code/Graph+Algorithms/Graph+TransitiveReduction.swift

+17-16
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,24 @@ public extension Graph
1111
*/
1212
func makeMinimumEquivalentGraph() -> Graph<NodeID, NodeValue>
1313
{
14-
var indirectReachabilities = Set<Edge>()
14+
var nonEssentialEdges = Set<Edge>()
1515
var consideredAncestorsHash = [Node: Nodes]()
1616

1717
for sourceNode in sources
1818
{
1919
// TODO: keep track of visited nodes within each traversal from a source and ignore already visited nodes so we can't get hung up in cycles
2020

21-
let reachabilities = findIndirectReachabilities(around: sourceNode,
22-
reachedAncestors: [],
23-
consideredAncestorsHash: &consideredAncestorsHash)
24-
25-
indirectReachabilities += reachabilities
21+
nonEssentialEdges += findNonEssentialEdges(around: sourceNode,
22+
reachedAncestors: [],
23+
consideredAncestorsHash: &consideredAncestorsHash)
2624
}
2725

28-
return copy(excludedEdges: indirectReachabilities)
26+
return copy(excludedEdges: nonEssentialEdges)
2927
}
3028

31-
private func findIndirectReachabilities(around node: Node,
32-
reachedAncestors: Nodes,
33-
consideredAncestorsHash: inout [Node: Nodes]) -> Set<Edge>
29+
private func findNonEssentialEdges(around node: Node,
30+
reachedAncestors: Nodes,
31+
consideredAncestorsHash: inout [Node: Nodes]) -> Set<Edge>
3432
{
3533
let consideredAncestors = consideredAncestorsHash[node, default: Nodes()]
3634
let ancestorsToConsider = reachedAncestors - consideredAncestors
@@ -43,7 +41,7 @@ public extension Graph
4341

4442
consideredAncestorsHash[node, default: Set<Node>()] += ancestorsToConsider
4543

46-
var indirectReachabilities = Set<Edge>()
44+
var nonEssentialEdges = Set<Edge>()
4745

4846
// base case: add edges from all reached ancestors to all reachable neighbours of node
4947

@@ -53,19 +51,22 @@ public extension Graph
5351
{
5452
for ancestor in ancestorsToConsider
5553
{
56-
indirectReachabilities += Edge(from: ancestor, to: descendant)
54+
if let nonEssentialEdge = edge(from: ancestor, to: descendant)
55+
{
56+
nonEssentialEdges += nonEssentialEdge
57+
}
5758
}
5859
}
5960

6061
// recursive calls on descendants
6162

6263
for descendant in descendants
6364
{
64-
indirectReachabilities += findIndirectReachabilities(around: descendant,
65-
reachedAncestors: ancestorsToConsider + node,
66-
consideredAncestorsHash: &consideredAncestorsHash)
65+
nonEssentialEdges += findNonEssentialEdges(around: descendant,
66+
reachedAncestors: ancestorsToConsider + node,
67+
consideredAncestorsHash: &consideredAncestorsHash)
6768
}
6869

69-
return indirectReachabilities
70+
return nonEssentialEdges
7071
}
7172
}

Code/Graph+Copying.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ extension Graph
1717
public func copy(includedNodes: OrderedSet<Node>? = nil,
1818
includedEdges: Set<Edge>? = nil) -> Graph<NodeID, NodeValue>
1919
{
20-
let actualIncludedNodes = includedNodes ?? OrderedSet(nodesByID.values)
20+
let myNodes = OrderedSet(nodesByID.values)
21+
22+
if !(includedNodes?.isSubset(of: myNodes) ?? true)
23+
{
24+
log(warning: "Some nodes to include in the Graph copy are not in the graph.")
25+
}
26+
27+
let actualIncludedNodes = includedNodes ?? myNodes
2128
let copiesOfIncludedNodes = actualIncludedNodes.map { Node(id: $0.id, value: $0.value) }
2229

2330
let graphCopy = Graph(nodes: OrderedSet(copiesOfIncludedNodes),

Code/Graph/Graph.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ public class Graph<NodeID: Hashable, NodeValue>
4949
to targetID: NodeID,
5050
count: Int = 1) -> Edge?
5151
{
52-
guard let source = node(for: sourceID), let target = node(for: targetID) else { return nil }
52+
guard let source = node(for: sourceID), let target = node(for: targetID) else
53+
{
54+
log(warning: "Tried to add edge between non-existing node IDs:\nsource ID = \(sourceID)\ntarget ID = \(targetID)")
55+
return nil
56+
}
57+
5358
return addEdge(from: source, to: target, count: count)
5459
}
5560

Tests/SwiftNodesTests.swift

+24-1
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,28 @@ class SwiftNodesTests: XCTestCase {
8484
XCTAssertEqual(graphCopy.edge(from: "id2", to: "id3")?.count, 2)
8585
}
8686

87-
// TODO: Test algorithms
87+
func testMinimumEquivalentGraph() {
88+
// make original graph
89+
let graph = Graph<String, Int> { "id\($0)" }
90+
91+
let node1 = graph.insert(1)
92+
let node2 = graph.insert(2)
93+
let node3 = graph.insert(3)
94+
95+
graph.addEdge(from: node1, to: node2)
96+
graph.addEdge(from: node2, to: node3)
97+
graph.addEdge(from: node1, to: node3)
98+
99+
XCTAssertEqual(graph.edges.count, 3)
100+
101+
// make MEG
102+
let meg = graph.makeMinimumEquivalentGraph()
103+
104+
XCTAssertEqual(meg.edges.count, 2)
105+
XCTAssertNotNil(meg.edge(from: "id1", to: "id2"))
106+
XCTAssertNotNil(meg.edge(from: "id2", to: "id3"))
107+
XCTAssertNil(meg.edge(from: "id1", to: "id3"))
108+
}
109+
110+
// TODO: Test more algorithms
88111
}

0 commit comments

Comments
 (0)