Skip to content

Commit ff4ac78

Browse files
committed
Graph library
0 parents  commit ff4ac78

File tree

77 files changed

+5777
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+5777
-0
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 László Teveli
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.resolved

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"originHash" : "e8d81c4ed867478e73dccb21d2e981e1710794cff51372edb0a3508f1639cfd5",
3+
"pins" : [
4+
{
5+
"identity" : "swift-algorithms",
6+
"kind" : "remoteSourceControl",
7+
"location" : "https://github.com/apple/swift-algorithms.git",
8+
"state" : {
9+
"revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
10+
"version" : "1.2.0"
11+
}
12+
},
13+
{
14+
"identity" : "swift-collections",
15+
"kind" : "remoteSourceControl",
16+
"location" : "https://github.com/apple/swift-collections.git",
17+
"state" : {
18+
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
19+
"version" : "1.1.4"
20+
}
21+
},
22+
{
23+
"identity" : "swift-numerics",
24+
"kind" : "remoteSourceControl",
25+
"location" : "https://github.com/apple/swift-numerics.git",
26+
"state" : {
27+
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
28+
"version" : "1.0.2"
29+
}
30+
}
31+
],
32+
"version" : 3
33+
}

Package.swift

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "Graphs",
7+
products: [
8+
.library(
9+
name: "Graphs",
10+
targets: ["Graphs"]
11+
)
12+
],
13+
dependencies: [
14+
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
15+
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.0")
16+
],
17+
targets: [
18+
.target(
19+
name: "Graphs",
20+
dependencies: [
21+
.product(name: "Collections", package: "swift-collections"),
22+
.product(name: "Algorithms", package: "swift-algorithms")
23+
],
24+
swiftSettings: [.swiftLanguageMode(.v6)]
25+
),
26+
.testTarget(
27+
name: "GraphsTests",
28+
dependencies: ["Graphs"],
29+
swiftSettings: [.swiftLanguageMode(.v6)]
30+
)
31+
]
32+
)

README.md

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/// A protocol that defines the requirements for a binary graph structure.
2+
public protocol BinaryGraphProtocol<Node, Edge>: GraphProtocol {
3+
/// The type of nodes in the graph.
4+
associatedtype Node
5+
/// The type of edges in the graph. Defaults to `Void`.
6+
associatedtype Edge = Void
7+
8+
/// Returns the binary edges originating from the specified node.
9+
/// - Parameter node: The node from which to get the edges.
10+
/// - Returns: A `BinaryGraphEdges` instance containing the edges from the specified node.
11+
@inlinable func edges(from node: Node) -> BinaryGraphEdges<Node, Edge>
12+
}
13+
14+
/// A structure representing the edges of a node in a binary graph.
15+
public struct BinaryGraphEdges<Node, Edge>: Container {
16+
/// The type of elements contained in the container.
17+
public typealias Element = GraphEdge<Node, Edge>
18+
19+
/// The source node of the edges.
20+
public var source: Node
21+
/// The left-hand side edge.
22+
public var lhs: Element?
23+
/// The right-hand side edge.
24+
public var rhs: Element?
25+
26+
/// Initializes a new `BinaryGraphEdges` instance with the given source node and edges.
27+
/// - Parameters:
28+
/// - source: The source node of the edges.
29+
/// - lhs: The left-hand side edge.
30+
/// - rhs: The right-hand side edge.
31+
@inlinable public init(source: Node, lhs: Element?, rhs: Element?) {
32+
self.source = source
33+
self.lhs = lhs
34+
self.rhs = rhs
35+
}
36+
37+
/// An array of elements contained in the container.
38+
@inlinable public var elements: [Element] { [lhs, rhs].compactMap(\.self) }
39+
}
40+
41+
extension GraphProtocol where Self: BinaryGraphProtocol {
42+
/// Returns the edges originating from the specified node.
43+
/// - Parameter node: The node from which to get the edges.
44+
/// - Returns: An array of `GraphEdge` instances containing the edges from the specified node.
45+
@inlinable public func edges(from node: Node) -> [GraphEdge<Node, Edge>] {
46+
let edges: BinaryGraphEdges<Node, Edge> = self.edges(from: node)
47+
return edges.elements
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// A protocol for bipartite graphs.
2+
public protocol BipartiteGraphProtocol<Node, Edge>: GraphProtocol where Node: Hashable {
3+
/// The left partition of the bipartite graph.
4+
var leftPartition: Set<Node> { get }
5+
/// The right partition of the bipartite graph.
6+
var rightPartition: Set<Node> { get }
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
extension GraphColoringAlgorithm {
2+
/// Creates a new instance of the greedy algorithm.
3+
@inlinable public static func greedy<Node, Edge>() -> Self where Self == GreedyColoringAlgorithm<Node, Edge> {
4+
GreedyColoringAlgorithm()
5+
}
6+
}
7+
8+
extension WholeGraphProtocol where Node: Hashable {
9+
/// Colors the nodes of the graph using the greedy algorithm.
10+
@inlinable public func colorNodes() -> [Node: Int] {
11+
colorNodes(using: .greedy())
12+
}
13+
}
14+
15+
/// An implementation of the greedy graph coloring algorithm.
16+
public struct GreedyColoringAlgorithm<Node: Hashable, Edge>: GraphColoringAlgorithm {
17+
/// Creates a new instance of the greedy algorithm.
18+
@inlinable public init() {}
19+
20+
/// Colors the nodes of the graph using the greedy algorithm.
21+
@inlinable public func coloring(
22+
of graph: some WholeGraphProtocol<Node, Edge>
23+
) -> [Node: Int] {
24+
var result: [Node: Int] = [:]
25+
let nodes = graph.allNodes.sorted { graph.edges(from: $0).count > graph.edges(from: $1).count }
26+
for node in nodes {
27+
var usedColors: Set<Int> = []
28+
for edge in graph.edges(from: node) {
29+
if let color = result[edge.destination] {
30+
usedColors.insert(color)
31+
}
32+
}
33+
// Find the smallest available color
34+
var color = 0
35+
while usedColors.contains(color) {
36+
color += 1
37+
}
38+
result[node] = color
39+
}
40+
return result
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extension WholeGraphProtocol where Node: Hashable {
2+
/// Colors the nodes of the graph using the specified algorithm.
3+
/// - Parameter algorithm: The algorithm to use for coloring the nodes.
4+
/// - Returns: A dictionary where the keys are the nodes and the values are the colors assigned to the nodes.
5+
@inlinable public func colorNodes(
6+
using algorithm: some GraphColoringAlgorithm<Node, Edge>
7+
) -> [Node: Int] {
8+
algorithm.coloring(of: self)
9+
}
10+
}
11+
12+
/// A protocol that defines the requirements for a graph coloring algorithm.
13+
public protocol GraphColoringAlgorithm<Node, Edge> {
14+
/// The type of nodes in the graph.
15+
associatedtype Node: Hashable
16+
/// The type of edges in the graph.
17+
associatedtype Edge
18+
19+
/// Colors the nodes of the graph.
20+
/// - Parameter graph: The graph in which to color the nodes.
21+
/// - Returns: A dictionary where the keys are the nodes and the values are the colors assigned to the nodes.
22+
@inlinable func coloring(
23+
of graph: some WholeGraphProtocol<Node, Edge>
24+
) -> [Node: Int]
25+
}

0 commit comments

Comments
 (0)