|
| 1 | +# Graph Library for Swift |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This Swift library provides a composable and extensible foundation for working with graphs. |
| 6 | +Whether you're constructing binary trees, performing complex graph traversals, or optimizing pathfinding algorithms on weighted graphs, this library offers the dynamic flexibility needed for a wide range of graph-related use cases. |
| 7 | + |
| 8 | +While some features are still in early development stages, the project is actively evolving, and contributions are highly encouraged. |
| 9 | + |
| 10 | +## Key Features |
| 11 | + |
| 12 | +- **Multiple Graph Types**: Supports general graphs, binary graphs, grid graphs, and lazy graphs (on-demand edge computation). |
| 13 | +- **Weighted Graphs**: Handles graphs with weighted edges, enabling specialized algorithms like shortest pathfinding. |
| 14 | +- **Traversals**: Breadth-First Search (BFS), Depth-First Search (DFS), with support for preorder, inorder, and postorder traversals. |
| 15 | +- **Traversal Strategies**: Includes unique node visiting, and depth, path, and cost tracking during traversals. |
| 16 | +- **Shortest Path Algorithms**: Dijkstra, Bellman-Ford, and A* algorithms for finding efficient routes. |
| 17 | +- **Eulerian and Hamiltonian Paths/Cycles**: Support for backtracking, heuristic-based, and Hierholzer's algorithm for Eulerian paths. |
| 18 | +- **Max Flow/Min Cut Algorithms**: Ford-Fulkerson, Edmonds-Karp, and Dinic's algorithms for network flow analysis. |
| 19 | +- **Minimum Spanning Tree Algorithms**: Kruskal's, Prim's, and Borůvka's algorithms for constructing minimum spanning trees. |
| 20 | +- **Strongly Connected Components**: Kosaraju’s and Tarjan’s algorithms for identifying strongly connected components. |
| 21 | +- **Graph Coloring**: Greedy algorithm for efficient node coloring. |
| 22 | +- **Maximum Bipartite Matching**: Hopcroft-Karp algorithm for bipartite matching. |
| 23 | + |
| 24 | +### Example Usage |
| 25 | + |
| 26 | +```swift |
| 27 | +let graph = Graph(edges: [ |
| 28 | + "Root": ["A", "B", "C"], |
| 29 | + "B": ["X", "Y", "Z"] |
| 30 | +]) |
| 31 | + |
| 32 | +graph.traverse(from: "Root", strategy: .bfs()) |
| 33 | +graph.traverse(from: "Root", strategy: .dfs()) |
| 34 | + |
| 35 | +graph.traverse(from: "Root", strategy: .bfs(.trackPath())) |
| 36 | +graph.traverse(from: "Root", strategy: .dfs(order: .postorder())) |
| 37 | +graph.traverse(from: "Root", strategy: .dfs().visitEachNodeOnce()) |
| 38 | + |
| 39 | +graph.shortestPath(from: "Root", to: "Z", using: .dijkstra()) // or .aStar() or .bellmanFord() |
| 40 | +graph.shortestPaths(from: "Root", using: .dijkstra()) // or .bellmanFord() |
| 41 | +graph.shortestPathsForAllPairs(using: .floydWarshall()) // or .johnson() |
| 42 | +graph.minimumSpanningTree(using: .kruskal()) // or .prim() or .boruvka() |
| 43 | +graph.maximumFlow(using: .fordFulkerson()) // or .edmondsKarp() or .dinic() |
| 44 | +graph.stronglyConnectedComponents(using: .kosaraju()) // or .tarjan() |
| 45 | + |
| 46 | +graph.colorNodes() |
| 47 | +graph.isCyclic() |
| 48 | +graph.isTree() |
| 49 | +graph.isConnected() |
| 50 | +graph.isPlanar() |
| 51 | +graph.isBipartite() |
| 52 | +graph.topologicalSort() |
| 53 | + |
| 54 | +let lazyGraph = LazyGraph { node in |
| 55 | + dynamicNeighbors(of: node) |
| 56 | +} |
| 57 | + |
| 58 | +let gridGraph = GridGraph(grid: [ |
| 59 | + ["A", "B", "C", "D", "E"], |
| 60 | + ["F", "G", "H", "I", "J"], |
| 61 | + ["K", "L", "M", "N", "O"], |
| 62 | + ["P", "Q", "R", "S", "T"], |
| 63 | + ["U", "V", "W", "X", "Y"] |
| 64 | +], availableDirections: .orthogonal).weightedByDistance() |
| 65 | + |
| 66 | +gridGraph.shortestPath(from: GridPosition(x: 0, y: 0), to: GridPosition(x: 4, y: 4), using: .aStar(heuristic: .euclideanDistance(of: \.coordinates))) |
| 67 | +gridGraph.shortestPaths(from: GridPosition(x: 0, y: 0)) |
| 68 | +gridGraph.shortestPathsForAllPairs() |
| 69 | + |
| 70 | +gridGraph.eulerianCycle() |
| 71 | +gridGraph.eulerianPath() |
| 72 | + |
| 73 | +gridGraph.hamiltonianCycle() |
| 74 | +gridGraph.hamiltonianPath() |
| 75 | +gridGraph.hamiltonianPath(from: GridPosition(x: 0, y: 0)) |
| 76 | +gridGraph.hamiltonianPath(from: GridPosition(x: 0, y: 0), to: GridPosition(x: 4, y: 4)) |
| 77 | + |
| 78 | +let binaryGraph = BinaryGraph(edges: [ |
| 79 | + "Root": (lhs: "A", rhs: "B"), |
| 80 | + "A": (lhs: "X", rhs: "Y"), |
| 81 | + "Y": (lhs: nil, rhs: "Z") |
| 82 | +]) |
| 83 | +binaryGraph.traverse(from: "Root", strategy: .dfs(order: .inorder())) |
| 84 | + |
| 85 | +let bipartite = Graph(edges: [ |
| 86 | + GraphEdge(source: "u1", destination: "v1"), |
| 87 | + GraphEdge(source: "u1", destination: "v2"), |
| 88 | + GraphEdge(source: "u2", destination: "v1"), |
| 89 | + GraphEdge(source: "u3", destination: "v2"), |
| 90 | + GraphEdge(source: "u3", destination: "v3"), |
| 91 | +]).bipartite(leftPartition: ["u1", "u2", "u3"], rightPartition: ["v1", "v2", "v3"]) |
| 92 | + |
| 93 | +bipartite.maximumMatching(using: .hopcroftKarp()) |
| 94 | +``` |
| 95 | + |
| 96 | +## Design Considerations |
| 97 | + |
| 98 | +### Generic Structure |
| 99 | + |
| 100 | +The library is built on a fully generic structure, allowing all nodes and edges to be defined as generic types without constraints. |
| 101 | +This flexibility enables seamless integration of various algorithms on specialized graphs, such as weighted graphs. |
| 102 | +By using generics, the library ensures that algorithms remain broadly applicable while optimized for specific graph types. |
| 103 | + |
| 104 | +For example, binary graphs can leverage specialized inorder depth-first search, while layered traversal strategies—such as unique node visits or path tracking—can be easily added to any algorithm. |
| 105 | +Generic constraints help formalize requirements, ensuring algorithms like Dijkstra's avoid negative weights for optimal performance. |
| 106 | + |
| 107 | +### Composable Components and Algorithms |
| 108 | + |
| 109 | +This library is designed with composability in mind. |
| 110 | +Similar to how Swift’s standard library transforms collections (e.g., `ReversedCollection`), this library provides efficient graph transformations such as `TransposedGraph` or `UndirectedGraph`. |
| 111 | +Algorithms can be layered and extended dynamically. |
| 112 | + |
| 113 | +### Strong Defaults with Flexibility |
| 114 | + |
| 115 | +Graphs in this library are directed by default, allowing for undirected (bidirectional) edges to be layered as transformations. |
| 116 | +Sensible algorithm defaults are provided, so users can easily get started without needing to specify algorithms manually. |
| 117 | +However, users retain the flexibility to override defaults with specific solutions when required. |
| 118 | + |
| 119 | +### Extensible Algorithms |
| 120 | + |
| 121 | +Algorithms in the library are built as abstract definitions with several built-in defaults. |
| 122 | +However, the architecture allows users to plug in their own algorithms if needed. |
| 123 | +For example, while Kruskal's and Prim's algorithms are provided for finding minimum spanning trees, users could implement their own reverse-delete algorithm, maintaining compatibility with the same API. |
| 124 | + |
| 125 | +### Flexible Graph Declarations |
| 126 | + |
| 127 | +The foundation of the library is the `GraphProtocol`, which requires only one method: defining the outgoing edges from a node. |
| 128 | +This minimalist design enables a wide range of algorithms – such as traversals, shortest paths, and minimum spanning trees – without requiring a complex interface. |
| 129 | + |
| 130 | +Multiple concrete graph types conform to this protocol, including eager representations (`Graph`, `BinaryGraph`) and optimized lazy evaluations (`LazyGraph`, `LazyBinaryGraph`). |
| 131 | +Specialized types like `WeightedGraph` manage weighted edges, while `GridGraph` provides a convenient structure for 2D grid-based graphs. |
| 132 | + |
| 133 | +## Contributions |
| 134 | + |
| 135 | +While the core library provides a solid foundation, certain areas are still under development and not fully production-tested. |
| 136 | +Contributions, suggestions, and feedback are highly encouraged. |
| 137 | +Feel free to submit issues, start discussions for feature requests, and contribute code via pull requests. |
0 commit comments