-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff4ac78
Showing
77 changed files
with
5,777 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/configuration/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 László Teveli | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"originHash" : "e8d81c4ed867478e73dccb21d2e981e1710794cff51372edb0a3508f1639cfd5", | ||
"pins" : [ | ||
{ | ||
"identity" : "swift-algorithms", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-algorithms.git", | ||
"state" : { | ||
"revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", | ||
"version" : "1.2.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-collections", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-collections.git", | ||
"state" : { | ||
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", | ||
"version" : "1.1.4" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-numerics", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-numerics.git", | ||
"state" : { | ||
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", | ||
"version" : "1.0.2" | ||
} | ||
} | ||
], | ||
"version" : 3 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// swift-tools-version: 6.0 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Graphs", | ||
products: [ | ||
.library( | ||
name: "Graphs", | ||
targets: ["Graphs"] | ||
) | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"), | ||
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.0") | ||
], | ||
targets: [ | ||
.target( | ||
name: "Graphs", | ||
dependencies: [ | ||
.product(name: "Collections", package: "swift-collections"), | ||
.product(name: "Algorithms", package: "swift-algorithms") | ||
], | ||
swiftSettings: [.swiftLanguageMode(.v6)] | ||
), | ||
.testTarget( | ||
name: "GraphsTests", | ||
dependencies: ["Graphs"], | ||
swiftSettings: [.swiftLanguageMode(.v6)] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# Graph Library for Swift | ||
|
||
## Overview | ||
|
||
This Swift library provides a composable and extensible foundation for working with graphs. | ||
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. | ||
|
||
While some features are still in early development stages, the project is actively evolving, and contributions are highly encouraged. | ||
|
||
## Key Features | ||
|
||
- **Multiple Graph Types**: Supports general graphs, binary graphs, grid graphs, and lazy graphs (on-demand edge computation). | ||
- **Weighted Graphs**: Handles graphs with weighted edges, enabling specialized algorithms like shortest pathfinding. | ||
- **Traversals**: Breadth-First Search (BFS), Depth-First Search (DFS), with support for preorder, inorder, and postorder traversals. | ||
- **Traversal Strategies**: Includes unique node visiting, and depth, path, and cost tracking during traversals. | ||
- **Shortest Path Algorithms**: Dijkstra, Bellman-Ford, and A* algorithms for finding efficient routes. | ||
- **Eulerian and Hamiltonian Paths/Cycles**: Support for backtracking, heuristic-based, and Hierholzer's algorithm for Eulerian paths. | ||
- **Max Flow/Min Cut Algorithms**: Ford-Fulkerson, Edmonds-Karp, and Dinic's algorithms for network flow analysis. | ||
- **Minimum Spanning Tree Algorithms**: Kruskal's, Prim's, and Borůvka's algorithms for constructing minimum spanning trees. | ||
- **Strongly Connected Components**: Kosaraju’s and Tarjan’s algorithms for identifying strongly connected components. | ||
- **Graph Coloring**: Greedy algorithm for efficient node coloring. | ||
- **Maximum Bipartite Matching**: Hopcroft-Karp algorithm for bipartite matching. | ||
|
||
### Example Usage | ||
|
||
```swift | ||
let graph = Graph(edges: [ | ||
"Root": ["A", "B", "C"], | ||
"B": ["X", "Y", "Z"] | ||
]) | ||
|
||
graph.traverse(from: "Root", strategy: .bfs()) | ||
graph.traverse(from: "Root", strategy: .dfs()) | ||
|
||
graph.traverse(from: "Root", strategy: .bfs(.trackPath())) | ||
graph.traverse(from: "Root", strategy: .dfs(order: .postorder())) | ||
graph.traverse(from: "Root", strategy: .dfs().visitEachNodeOnce()) | ||
|
||
graph.shortestPath(from: "Root", to: "Z", using: .dijkstra()) // or .aStar() or .bellmanFord() | ||
graph.shortestPaths(from: "Root", using: .dijkstra()) // or .bellmanFord() | ||
graph.shortestPathsForAllPairs(using: .floydWarshall()) // or .johnson() | ||
graph.minimumSpanningTree(using: .kruskal()) // or .prim() or .boruvka() | ||
graph.maximumFlow(using: .fordFulkerson()) // or .edmondsKarp() or .dinic() | ||
graph.stronglyConnectedComponents(using: .kosaraju()) // or .tarjan() | ||
|
||
graph.colorNodes() | ||
graph.isCyclic() | ||
graph.isTree() | ||
graph.isConnected() | ||
graph.isPlanar() | ||
graph.isBipartite() | ||
graph.topologicalSort() | ||
|
||
let lazyGraph = LazyGraph { node in | ||
dynamicNeighbors(of: node) | ||
} | ||
|
||
let gridGraph = GridGraph(grid: [ | ||
["A", "B", "C", "D", "E"], | ||
["F", "G", "H", "I", "J"], | ||
["K", "L", "M", "N", "O"], | ||
["P", "Q", "R", "S", "T"], | ||
["U", "V", "W", "X", "Y"] | ||
], availableDirections: .orthogonal).weightedByDistance() | ||
|
||
gridGraph.shortestPath(from: GridPosition(x: 0, y: 0), to: GridPosition(x: 4, y: 4), using: .aStar(heuristic: .euclideanDistance(of: \.coordinates))) | ||
gridGraph.shortestPaths(from: GridPosition(x: 0, y: 0)) | ||
gridGraph.shortestPathsForAllPairs() | ||
|
||
gridGraph.eulerianCycle() | ||
gridGraph.eulerianPath() | ||
|
||
gridGraph.hamiltonianCycle() | ||
gridGraph.hamiltonianPath() | ||
gridGraph.hamiltonianPath(from: GridPosition(x: 0, y: 0)) | ||
gridGraph.hamiltonianPath(from: GridPosition(x: 0, y: 0), to: GridPosition(x: 4, y: 4)) | ||
|
||
let binaryGraph = BinaryGraph(edges: [ | ||
"Root": (lhs: "A", rhs: "B"), | ||
"A": (lhs: "X", rhs: "Y"), | ||
"Y": (lhs: nil, rhs: "Z") | ||
]) | ||
binaryGraph.traverse(from: "Root", strategy: .dfs(order: .inorder())) | ||
|
||
let bipartite = Graph(edges: [ | ||
GraphEdge(source: "u1", destination: "v1"), | ||
GraphEdge(source: "u1", destination: "v2"), | ||
GraphEdge(source: "u2", destination: "v1"), | ||
GraphEdge(source: "u3", destination: "v2"), | ||
GraphEdge(source: "u3", destination: "v3"), | ||
]).bipartite(leftPartition: ["u1", "u2", "u3"], rightPartition: ["v1", "v2", "v3"]) | ||
|
||
bipartite.maximumMatching(using: .hopcroftKarp()) | ||
``` | ||
|
||
## Design Considerations | ||
|
||
### Generic Structure | ||
|
||
The library is built on a fully generic structure, allowing all nodes and edges to be defined as generic types without constraints. | ||
This flexibility enables seamless integration of various algorithms on specialized graphs, such as weighted graphs. | ||
By using generics, the library ensures that algorithms remain broadly applicable while optimized for specific graph types. | ||
|
||
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. | ||
Generic constraints help formalize requirements, ensuring algorithms like Dijkstra's avoid negative weights for optimal performance. | ||
|
||
### Composable Components and Algorithms | ||
|
||
This library is designed with composability in mind. | ||
Similar to how Swift’s standard library transforms collections (e.g., `ReversedCollection`), this library provides efficient graph transformations such as `TransposedGraph` or `UndirectedGraph`. | ||
Algorithms can be layered and extended dynamically. | ||
|
||
### Strong Defaults with Flexibility | ||
|
||
Graphs in this library are directed by default, allowing for undirected (bidirectional) edges to be layered as transformations. | ||
Sensible algorithm defaults are provided, so users can easily get started without needing to specify algorithms manually. | ||
However, users retain the flexibility to override defaults with specific solutions when required. | ||
|
||
### Extensible Algorithms | ||
|
||
Algorithms in the library are built as abstract definitions with several built-in defaults. | ||
However, the architecture allows users to plug in their own algorithms if needed. | ||
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. | ||
|
||
### Flexible Graph Declarations | ||
|
||
The foundation of the library is the `GraphProtocol`, which requires only one method: defining the outgoing edges from a node. | ||
This minimalist design enables a wide range of algorithms – such as traversals, shortest paths, and minimum spanning trees – without requiring a complex interface. | ||
|
||
Multiple concrete graph types conform to this protocol, including eager representations (`Graph`, `BinaryGraph`) and optimized lazy evaluations (`LazyGraph`, `LazyBinaryGraph`). | ||
Specialized types like `WeightedGraph` manage weighted edges, while `GridGraph` provides a convenient structure for 2D grid-based graphs. | ||
|
||
## Contributions | ||
|
||
While the core library provides a solid foundation, certain areas are still under development and not fully production-tested. | ||
Contributions, suggestions, and feedback are highly encouraged. | ||
Feel free to submit issues, start discussions for feature requests, and contribute code via pull requests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/// A protocol that defines the requirements for a binary graph structure. | ||
public protocol BinaryGraphProtocol<Node, Edge>: GraphProtocol { | ||
/// The type of nodes in the graph. | ||
associatedtype Node | ||
/// The type of edges in the graph. Defaults to `Void`. | ||
associatedtype Edge = Void | ||
|
||
/// Returns the binary edges originating from the specified node. | ||
/// - Parameter node: The node from which to get the edges. | ||
/// - Returns: A `BinaryGraphEdges` instance containing the edges from the specified node. | ||
@inlinable func edges(from node: Node) -> BinaryGraphEdges<Node, Edge> | ||
} | ||
|
||
/// A structure representing the edges of a node in a binary graph. | ||
public struct BinaryGraphEdges<Node, Edge>: Container { | ||
/// The type of elements contained in the container. | ||
public typealias Element = GraphEdge<Node, Edge> | ||
|
||
/// The source node of the edges. | ||
public var source: Node | ||
/// The left-hand side edge. | ||
public var lhs: Element? | ||
/// The right-hand side edge. | ||
public var rhs: Element? | ||
|
||
/// Initializes a new `BinaryGraphEdges` instance with the given source node and edges. | ||
/// - Parameters: | ||
/// - source: The source node of the edges. | ||
/// - lhs: The left-hand side edge. | ||
/// - rhs: The right-hand side edge. | ||
@inlinable public init(source: Node, lhs: Element?, rhs: Element?) { | ||
self.source = source | ||
self.lhs = lhs | ||
self.rhs = rhs | ||
} | ||
|
||
/// An array of elements contained in the container. | ||
@inlinable public var elements: [Element] { [lhs, rhs].compactMap(\.self) } | ||
} | ||
|
||
extension GraphProtocol where Self: BinaryGraphProtocol { | ||
/// Returns the edges originating from the specified node. | ||
/// - Parameter node: The node from which to get the edges. | ||
/// - Returns: An array of `GraphEdge` instances containing the edges from the specified node. | ||
@inlinable public func edges(from node: Node) -> [GraphEdge<Node, Edge>] { | ||
let edges: BinaryGraphEdges<Node, Edge> = self.edges(from: node) | ||
return edges.elements | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/// A protocol for bipartite graphs. | ||
public protocol BipartiteGraphProtocol<Node, Edge>: GraphProtocol where Node: Hashable { | ||
/// The left partition of the bipartite graph. | ||
var leftPartition: Set<Node> { get } | ||
/// The right partition of the bipartite graph. | ||
var rightPartition: Set<Node> { get } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
extension GraphColoringAlgorithm { | ||
/// Creates a new instance of the greedy algorithm. | ||
@inlinable public static func greedy<Node, Edge>() -> Self where Self == GreedyColoringAlgorithm<Node, Edge> { | ||
GreedyColoringAlgorithm() | ||
} | ||
} | ||
|
||
extension WholeGraphProtocol where Node: Hashable { | ||
/// Colors the nodes of the graph using the greedy algorithm. | ||
@inlinable public func colorNodes() -> [Node: Int] { | ||
colorNodes(using: .greedy()) | ||
} | ||
} | ||
|
||
/// An implementation of the greedy graph coloring algorithm. | ||
public struct GreedyColoringAlgorithm<Node: Hashable, Edge>: GraphColoringAlgorithm { | ||
/// Creates a new instance of the greedy algorithm. | ||
@inlinable public init() {} | ||
|
||
/// Colors the nodes of the graph using the greedy algorithm. | ||
@inlinable public func coloring( | ||
of graph: some WholeGraphProtocol<Node, Edge> | ||
) -> [Node: Int] { | ||
var result: [Node: Int] = [:] | ||
let nodes = graph.allNodes.sorted { graph.edges(from: $0).count > graph.edges(from: $1).count } | ||
for node in nodes { | ||
var usedColors: Set<Int> = [] | ||
for edge in graph.edges(from: node) { | ||
if let color = result[edge.destination] { | ||
usedColors.insert(color) | ||
} | ||
} | ||
// Find the smallest available color | ||
var color = 0 | ||
while usedColors.contains(color) { | ||
color += 1 | ||
} | ||
result[node] = color | ||
} | ||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
extension WholeGraphProtocol where Node: Hashable { | ||
/// Colors the nodes of the graph using the specified algorithm. | ||
/// - Parameter algorithm: The algorithm to use for coloring the nodes. | ||
/// - Returns: A dictionary where the keys are the nodes and the values are the colors assigned to the nodes. | ||
@inlinable public func colorNodes( | ||
using algorithm: some GraphColoringAlgorithm<Node, Edge> | ||
) -> [Node: Int] { | ||
algorithm.coloring(of: self) | ||
} | ||
} | ||
|
||
/// A protocol that defines the requirements for a graph coloring algorithm. | ||
public protocol GraphColoringAlgorithm<Node, Edge> { | ||
/// The type of nodes in the graph. | ||
associatedtype Node: Hashable | ||
/// The type of edges in the graph. | ||
associatedtype Edge | ||
|
||
/// Colors the nodes of the graph. | ||
/// - Parameter graph: The graph in which to color the nodes. | ||
/// - Returns: A dictionary where the keys are the nodes and the values are the colors assigned to the nodes. | ||
@inlinable func coloring( | ||
of graph: some WholeGraphProtocol<Node, Edge> | ||
) -> [Node: Int] | ||
} |
Oops, something went wrong.