Skip to content

Commit 0ff7e7b

Browse files
authored
feat: add Visitor pattern and Graph data structures for compiler optimization (#14)
1 parent 1da20a7 commit 0ff7e7b

File tree

4 files changed

+260
-0
lines changed

4 files changed

+260
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// Graph.swift
3+
// DesignAlgorithmsKit
4+
//
5+
// Graph Data Structures and Algorithms
6+
//
7+
8+
import Foundation
9+
10+
/// A basic node in a graph.
11+
public struct GraphNode<T: Hashable>: Hashable, Identifiable {
12+
public let id: T
13+
public let data: T
14+
15+
public init(_ data: T) {
16+
self.data = data
17+
self.id = data
18+
}
19+
}
20+
21+
/// A generic Graph implemented using an Adjacency List.
22+
public class Graph<T: Hashable> {
23+
24+
private var adjList: [T: [T]] = [:]
25+
private var nodes: [T: GraphNode<T>] = [:]
26+
27+
public init() {}
28+
29+
/// Adds a node to the graph.
30+
/// - Parameter data: The data associated with the node.
31+
@discardableResult
32+
public func addNode(_ data: T) -> GraphNode<T> {
33+
if let existing = nodes[data] {
34+
return existing
35+
}
36+
let node = GraphNode(data)
37+
nodes[data] = node
38+
adjList[data] = []
39+
return node
40+
}
41+
42+
/// Adds a directed edge from source to destination.
43+
public func addEdge(from source: T, to destination: T) {
44+
addNode(source)
45+
addNode(destination)
46+
adjList[source]?.append(destination)
47+
}
48+
49+
/// Returns the neighbors of a node.
50+
public func neighbors(of node: T) -> [T] {
51+
return adjList[node] ?? []
52+
}
53+
54+
/// Returns all nodes in the graph.
55+
public var allNodes: [T] {
56+
return Array(nodes.keys)
57+
}
58+
}
59+
60+
// MARK: - Algorithms
61+
62+
extension Graph {
63+
64+
/// Performs Breadth-First Search (BFS) to find all reachable nodes from a set of roots.
65+
/// - Parameter roots: The starting nodes.
66+
/// - Returns: A Set of reachable node IDs.
67+
public func reachability(from roots: [T]) -> Set<T> {
68+
var visited = Set<T>()
69+
var queue = roots
70+
71+
// Mark initial roots as visited if they exist in graph
72+
for root in roots {
73+
if nodes[root] != nil {
74+
visited.insert(root)
75+
}
76+
}
77+
78+
while !queue.isEmpty {
79+
let current = queue.removeFirst()
80+
81+
guard let neighbors = adjList[current] else { continue }
82+
83+
for neighbor in neighbors {
84+
if !visited.contains(neighbor) {
85+
visited.insert(neighbor)
86+
queue.append(neighbor)
87+
}
88+
}
89+
}
90+
91+
return visited
92+
}
93+
94+
/// Performs "Tree Shaking" - finding all unreachable nodes.
95+
/// - Parameter roots: The root nodes (entry points).
96+
/// - Returns: A Set of unreachable node IDs (candidates for removal).
97+
public func findUnreachable(from roots: [T]) -> Set<T> {
98+
let reachable = reachability(from: roots)
99+
let all = Set(allNodes)
100+
return all.subtracting(reachable)
101+
}
102+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// Visitor.swift
3+
// DesignAlgorithmsKit
4+
//
5+
// Visitor Pattern - Separate algorithms from object structure
6+
//
7+
8+
import Foundation
9+
10+
/// A generic visitor interface.
11+
/// Allows for separation of algorithms from the object structure they operate on.
12+
///
13+
/// - Result: The type of result produced by the visit.
14+
public protocol Visitor {
15+
associatedtype Result
16+
17+
/// Visits a visitable element.
18+
/// In a concrete implementation, this would likely dispatch to specific methods based on type.
19+
/// However, since Swift doesn't support double dispatch natively without manual casting or overloading
20+
/// in the `accept` method of the element, strict type safety often requires the Element to call a specific method on the Visitor.
21+
///
22+
/// For a generic protocol, we define the entry point.
23+
func visit(_ element: Visitable) -> Result
24+
}
25+
26+
/// An interface for elements that can be visited.
27+
public protocol Visitable {
28+
/// Accepts a visitor.
29+
/// - Parameter visitor: The visitor instance.
30+
/// - Returns: The result of the visit.
31+
func accept<V: Visitor>(_ visitor: V) -> V.Result
32+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// GraphTests.swift
3+
// DesignAlgorithmsKitTests
4+
//
5+
// Tests for Graph Data Structures and Algorithms
6+
//
7+
8+
import XCTest
9+
@testable import DesignAlgorithmsKit
10+
11+
final class GraphTests: XCTestCase {
12+
13+
func testGraphConstruction() {
14+
let graph = Graph<String>()
15+
graph.addNode("A")
16+
graph.addEdge(from: "A", to: "B")
17+
18+
XCTAssertEqual(graph.allNodes.count, 2)
19+
XCTAssertTrue(graph.neighbors(of: "A").contains("B"))
20+
XCTAssertTrue(graph.neighbors(of: "B").isEmpty)
21+
}
22+
23+
func testReachabilitySimple() {
24+
let graph = Graph<String>()
25+
graph.addEdge(from: "A", to: "B")
26+
graph.addEdge(from: "B", to: "C")
27+
28+
let reachable = graph.reachability(from: ["A"])
29+
30+
XCTAssertTrue(reachable.contains("A"))
31+
XCTAssertTrue(reachable.contains("B"))
32+
XCTAssertTrue(reachable.contains("C"))
33+
XCTAssertEqual(reachable.count, 3)
34+
}
35+
36+
func testReachabilityCycle() {
37+
let graph = Graph<String>()
38+
graph.addEdge(from: "A", to: "B")
39+
graph.addEdge(from: "B", to: "A") // Cycle
40+
41+
let reachable = graph.reachability(from: ["A"])
42+
43+
XCTAssertTrue(reachable.contains("A"))
44+
XCTAssertTrue(reachable.contains("B"))
45+
XCTAssertEqual(reachable.count, 2)
46+
}
47+
48+
func testTreeShaking() {
49+
let graph = Graph<String>()
50+
// Reachable Component
51+
graph.addEdge(from: "Root", to: "A")
52+
graph.addEdge(from: "A", to: "B")
53+
54+
// Unreachable Component (Dead Code)
55+
graph.addEdge(from: "Dead", to: "MoreDead")
56+
57+
let unreachable = graph.findUnreachable(from: ["Root"])
58+
59+
XCTAssertTrue(unreachable.contains("Dead"))
60+
XCTAssertTrue(unreachable.contains("MoreDead"))
61+
XCTAssertFalse(unreachable.contains("Root"))
62+
XCTAssertFalse(unreachable.contains("A"))
63+
}
64+
65+
func testDisconnectedNodes() {
66+
let graph = Graph<Int>()
67+
graph.addNode(1)
68+
graph.addNode(2)
69+
70+
let reachable = graph.reachability(from: [1])
71+
XCTAssertTrue(reachable.contains(1))
72+
XCTAssertFalse(reachable.contains(2))
73+
}
74+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// VisitorTests.swift
3+
// DesignAlgorithmsKitTests
4+
//
5+
// Tests for Visitor Pattern
6+
//
7+
8+
import XCTest
9+
@testable import DesignAlgorithmsKit
10+
11+
final class VisitorTests: XCTestCase {
12+
13+
// Mocks
14+
struct ConcreteElement: Visitable {
15+
let name: String
16+
func accept<V: Visitor>(_ visitor: V) -> V.Result {
17+
return visitor.visit(self)
18+
}
19+
}
20+
21+
struct NameVisitor: Visitor {
22+
typealias Result = String
23+
24+
func visit(_ element: Visitable) -> String {
25+
if let ce = element as? ConcreteElement {
26+
return "Visited: \(ce.name)"
27+
}
28+
return "Unknown"
29+
}
30+
}
31+
32+
func testVisitorTraverse() {
33+
let element = ConcreteElement(name: "TestNode")
34+
let visitor = NameVisitor()
35+
36+
let result = element.accept(visitor)
37+
XCTAssertEqual(result, "Visited: TestNode")
38+
}
39+
40+
func testVisitorUnknownType() {
41+
struct UnknownElement: Visitable {
42+
func accept<V: Visitor>(_ visitor: V) -> V.Result {
43+
return visitor.visit(self)
44+
}
45+
}
46+
47+
let element = UnknownElement()
48+
let visitor = NameVisitor()
49+
let result = element.accept(visitor)
50+
XCTAssertEqual(result, "Unknown")
51+
}
52+
}

0 commit comments

Comments
 (0)