Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions Sources/DesignAlgorithmsKit/Algorithms/DataStructures/Graph.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// Graph.swift
// DesignAlgorithmsKit
//
// Graph Data Structures and Algorithms
//

import Foundation

/// A basic node in a graph.
public struct GraphNode<T: Hashable>: 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<T: Hashable> {

private var adjList: [T: [T]] = [:]
private var nodes: [T: GraphNode<T>] = [:]

public init() {}

/// Adds a node to the graph.
/// - Parameter data: The data associated with the node.
@discardableResult
public func addNode(_ data: T) -> GraphNode<T> {
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<T> {
var visited = Set<T>()
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<T> {
let reachable = reachability(from: roots)
let all = Set(allNodes)
return all.subtracting(reachable)
}
}
32 changes: 32 additions & 0 deletions Sources/DesignAlgorithmsKit/Behavioral/Visitor.swift
Original file line number Diff line number Diff line change
@@ -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<V: Visitor>(_ visitor: V) -> V.Result
}
74 changes: 74 additions & 0 deletions Tests/DesignAlgorithmsKitTests/Algorithms/GraphTests.swift
Original file line number Diff line number Diff line change
@@ -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<String>()
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<String>()
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<String>()
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<String>()
// 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<Int>()
graph.addNode(1)
graph.addNode(2)

let reachable = graph.reachability(from: [1])
XCTAssertTrue(reachable.contains(1))
XCTAssertFalse(reachable.contains(2))
}
}
52 changes: 52 additions & 0 deletions Tests/DesignAlgorithmsKitTests/Behavioral/VisitorTests.swift
Original file line number Diff line number Diff line change
@@ -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<V: Visitor>(_ 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<V: Visitor>(_ visitor: V) -> V.Result {
return visitor.visit(self)
}
}

let element = UnknownElement()
let visitor = NameVisitor()
let result = element.accept(visitor)
XCTAssertEqual(result, "Unknown")
}
}