From 29cd706169242c91b1bb2182780480d113b2b1af Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Wed, 31 Dec 2025 07:33:50 -0600 Subject: [PATCH] feat: add Visitor pattern and Graph data structures for compiler optimization --- .../Algorithms/DataStructures/Graph.swift | 102 ++++++++++++++++++ .../Behavioral/Visitor.swift | 32 ++++++ .../Algorithms/GraphTests.swift | 74 +++++++++++++ .../Behavioral/VisitorTests.swift | 52 +++++++++ 4 files changed, 260 insertions(+) create mode 100644 Sources/DesignAlgorithmsKit/Algorithms/DataStructures/Graph.swift create mode 100644 Sources/DesignAlgorithmsKit/Behavioral/Visitor.swift create mode 100644 Tests/DesignAlgorithmsKitTests/Algorithms/GraphTests.swift create mode 100644 Tests/DesignAlgorithmsKitTests/Behavioral/VisitorTests.swift diff --git a/Sources/DesignAlgorithmsKit/Algorithms/DataStructures/Graph.swift b/Sources/DesignAlgorithmsKit/Algorithms/DataStructures/Graph.swift new file mode 100644 index 0000000..56ffb4f --- /dev/null +++ b/Sources/DesignAlgorithmsKit/Algorithms/DataStructures/Graph.swift @@ -0,0 +1,102 @@ +// +// Graph.swift +// DesignAlgorithmsKit +// +// Graph Data Structures and Algorithms +// + +import Foundation + +/// A basic node in a graph. +public struct GraphNode: Hashable, Identifiable { + public let id: T + public let data: T + + public init(_ data: T) { + self.data = data + self.id = data + } +} + +/// A generic Graph implemented using an Adjacency List. +public class Graph { + + private var adjList: [T: [T]] = [:] + private var nodes: [T: GraphNode] = [:] + + public init() {} + + /// Adds a node to the graph. + /// - Parameter data: The data associated with the node. + @discardableResult + public func addNode(_ data: T) -> GraphNode { + if let existing = nodes[data] { + return existing + } + let node = GraphNode(data) + nodes[data] = node + adjList[data] = [] + return node + } + + /// Adds a directed edge from source to destination. + public func addEdge(from source: T, to destination: T) { + addNode(source) + addNode(destination) + adjList[source]?.append(destination) + } + + /// Returns the neighbors of a node. + public func neighbors(of node: T) -> [T] { + return adjList[node] ?? [] + } + + /// Returns all nodes in the graph. + public var allNodes: [T] { + return Array(nodes.keys) + } +} + +// MARK: - Algorithms + +extension Graph { + + /// Performs Breadth-First Search (BFS) to find all reachable nodes from a set of roots. + /// - Parameter roots: The starting nodes. + /// - Returns: A Set of reachable node IDs. + public func reachability(from roots: [T]) -> Set { + var visited = Set() + var queue = roots + + // Mark initial roots as visited if they exist in graph + for root in roots { + if nodes[root] != nil { + visited.insert(root) + } + } + + while !queue.isEmpty { + let current = queue.removeFirst() + + guard let neighbors = adjList[current] else { continue } + + for neighbor in neighbors { + if !visited.contains(neighbor) { + visited.insert(neighbor) + queue.append(neighbor) + } + } + } + + return visited + } + + /// Performs "Tree Shaking" - finding all unreachable nodes. + /// - Parameter roots: The root nodes (entry points). + /// - Returns: A Set of unreachable node IDs (candidates for removal). + public func findUnreachable(from roots: [T]) -> Set { + let reachable = reachability(from: roots) + let all = Set(allNodes) + return all.subtracting(reachable) + } +} diff --git a/Sources/DesignAlgorithmsKit/Behavioral/Visitor.swift b/Sources/DesignAlgorithmsKit/Behavioral/Visitor.swift new file mode 100644 index 0000000..8aa19f1 --- /dev/null +++ b/Sources/DesignAlgorithmsKit/Behavioral/Visitor.swift @@ -0,0 +1,32 @@ +// +// Visitor.swift +// DesignAlgorithmsKit +// +// Visitor Pattern - Separate algorithms from object structure +// + +import Foundation + +/// A generic visitor interface. +/// Allows for separation of algorithms from the object structure they operate on. +/// +/// - Result: The type of result produced by the visit. +public protocol Visitor { + associatedtype Result + + /// Visits a visitable element. + /// In a concrete implementation, this would likely dispatch to specific methods based on type. + /// However, since Swift doesn't support double dispatch natively without manual casting or overloading + /// in the `accept` method of the element, strict type safety often requires the Element to call a specific method on the Visitor. + /// + /// For a generic protocol, we define the entry point. + func visit(_ element: Visitable) -> Result +} + +/// An interface for elements that can be visited. +public protocol Visitable { + /// Accepts a visitor. + /// - Parameter visitor: The visitor instance. + /// - Returns: The result of the visit. + func accept(_ visitor: V) -> V.Result +} diff --git a/Tests/DesignAlgorithmsKitTests/Algorithms/GraphTests.swift b/Tests/DesignAlgorithmsKitTests/Algorithms/GraphTests.swift new file mode 100644 index 0000000..088f3ff --- /dev/null +++ b/Tests/DesignAlgorithmsKitTests/Algorithms/GraphTests.swift @@ -0,0 +1,74 @@ +// +// GraphTests.swift +// DesignAlgorithmsKitTests +// +// Tests for Graph Data Structures and Algorithms +// + +import XCTest +@testable import DesignAlgorithmsKit + +final class GraphTests: XCTestCase { + + func testGraphConstruction() { + let graph = Graph() + graph.addNode("A") + graph.addEdge(from: "A", to: "B") + + XCTAssertEqual(graph.allNodes.count, 2) + XCTAssertTrue(graph.neighbors(of: "A").contains("B")) + XCTAssertTrue(graph.neighbors(of: "B").isEmpty) + } + + func testReachabilitySimple() { + let graph = Graph() + graph.addEdge(from: "A", to: "B") + graph.addEdge(from: "B", to: "C") + + let reachable = graph.reachability(from: ["A"]) + + XCTAssertTrue(reachable.contains("A")) + XCTAssertTrue(reachable.contains("B")) + XCTAssertTrue(reachable.contains("C")) + XCTAssertEqual(reachable.count, 3) + } + + func testReachabilityCycle() { + let graph = Graph() + graph.addEdge(from: "A", to: "B") + graph.addEdge(from: "B", to: "A") // Cycle + + let reachable = graph.reachability(from: ["A"]) + + XCTAssertTrue(reachable.contains("A")) + XCTAssertTrue(reachable.contains("B")) + XCTAssertEqual(reachable.count, 2) + } + + func testTreeShaking() { + let graph = Graph() + // Reachable Component + graph.addEdge(from: "Root", to: "A") + graph.addEdge(from: "A", to: "B") + + // Unreachable Component (Dead Code) + graph.addEdge(from: "Dead", to: "MoreDead") + + let unreachable = graph.findUnreachable(from: ["Root"]) + + XCTAssertTrue(unreachable.contains("Dead")) + XCTAssertTrue(unreachable.contains("MoreDead")) + XCTAssertFalse(unreachable.contains("Root")) + XCTAssertFalse(unreachable.contains("A")) + } + + func testDisconnectedNodes() { + let graph = Graph() + graph.addNode(1) + graph.addNode(2) + + let reachable = graph.reachability(from: [1]) + XCTAssertTrue(reachable.contains(1)) + XCTAssertFalse(reachable.contains(2)) + } +} diff --git a/Tests/DesignAlgorithmsKitTests/Behavioral/VisitorTests.swift b/Tests/DesignAlgorithmsKitTests/Behavioral/VisitorTests.swift new file mode 100644 index 0000000..c1474f5 --- /dev/null +++ b/Tests/DesignAlgorithmsKitTests/Behavioral/VisitorTests.swift @@ -0,0 +1,52 @@ +// +// VisitorTests.swift +// DesignAlgorithmsKitTests +// +// Tests for Visitor Pattern +// + +import XCTest +@testable import DesignAlgorithmsKit + +final class VisitorTests: XCTestCase { + + // Mocks + struct ConcreteElement: Visitable { + let name: String + func accept(_ visitor: V) -> V.Result { + return visitor.visit(self) + } + } + + struct NameVisitor: Visitor { + typealias Result = String + + func visit(_ element: Visitable) -> String { + if let ce = element as? ConcreteElement { + return "Visited: \(ce.name)" + } + return "Unknown" + } + } + + func testVisitorTraverse() { + let element = ConcreteElement(name: "TestNode") + let visitor = NameVisitor() + + let result = element.accept(visitor) + XCTAssertEqual(result, "Visited: TestNode") + } + + func testVisitorUnknownType() { + struct UnknownElement: Visitable { + func accept(_ visitor: V) -> V.Result { + return visitor.visit(self) + } + } + + let element = UnknownElement() + let visitor = NameVisitor() + let result = element.accept(visitor) + XCTAssertEqual(result, "Unknown") + } +}