A comprehensive, high-performance graph algorithms library for Swift, inspired by the Boost Graph Library. Built on protocol-oriented design for maximum flexibility, type safety, and performance across all Swift platforms.
import Graphs
// Create a graph
var graph = AdjacencyList()
graph.add(edges: [
("s", "a", 3.0),
("s", "b", 5.0),
("a", "c", 2.0),
("b", "c", 1.0),
("c", "d", 4.0)
])
// Find shortest path
let path = graph.shortestPath(from: "s", to: "d", using: .dijkstra())
print(path?.vertices) // ["s", "b", "c", "d"]
// Traverse the graph
let dfs = graph.traverse(from: "s", using: .dfs())
print(dfs.vertices) // ["s", "a", "c", "d", "b"]
// Grid pathfinding with A*
let grid = GridGraph(width: 10, height: 10)
let path = grid.shortestPath(
from: .init(x: 0, y: 0),
to: .init(x: 9, y: 9),
using: .aStar(heuristic: .manhattanDistance)
)Build a city transportation network and explore all available algorithms:
import Graphs
// Create a transportation network
var cityNetwork = AdjacencyList()
// Add cities as vertices with population data
let sanFrancisco = cityNetwork.addVertex { $0.name = "San Francisco"; $0.population = 815201 }
let losAngeles = cityNetwork.addVertex { $0.name = "Los Angeles"; $0.population = 3820914 }
let sanDiego = cityNetwork.addVertex { $0.name = "San Diego"; $0.population = 1381611 }
let lasVegas = cityNetwork.addVertex { $0.name = "Las Vegas"; $0.population = 641903 }
let phoenix = cityNetwork.addVertex { $0.name = "Phoenix"; $0.population = 1608139 }
let denver = cityNetwork.addVertex { $0.name = "Denver"; $0.population = 715522 }
// Add routes with distances (in miles)
cityNetwork.addEdge(from: sanFrancisco, to: losAngeles) { $0.weight = 380.0 }
cityNetwork.addEdge(from: losAngeles, to: sanDiego) { $0.weight = 120.0 }
cityNetwork.addEdge(from: losAngeles, to: lasVegas) { $0.weight = 270.0 }
cityNetwork.addEdge(from: lasVegas, to: phoenix) { $0.weight = 300.0 }
cityNetwork.addEdge(from: phoenix, to: denver) { $0.weight = 600.0 }
cityNetwork.addEdge(from: sanFrancisco, to: lasVegas) { $0.weight = 570.0 }
cityNetwork.addEdge(from: sanDiego, to: phoenix) { $0.weight = 355.0 }
// Graph Traversal - explores vertices in different orders
cityNetwork.traverse(from: sanFrancisco)
cityNetwork.traverse(from: sanFrancisco, using: .bfs())
cityNetwork.traverse(from: sanFrancisco, using: .dfs())
cityNetwork.traverse(from: sanFrancisco, using: .dfs(order: .preorder))
cityNetwork.traverse(from: sanFrancisco, using: .dfs(order: .postorder))
cityNetwork.traverse(from: sanFrancisco, using: .bestFirst(heuristic: { _ in 0 }))
cityNetwork.traverse(from: sanFrancisco, using: .depthLimitedDFS(maxDepth: 3))
cityNetwork.traverse(from: sanFrancisco, using: .iterativelyDeepeningDFS(maxDepth: 5))
// Search - lazy sequence iteration for on-demand exploration
cityNetwork.search(from: sanFrancisco)
cityNetwork.search(from: sanFrancisco, using: .dfs())
cityNetwork.search(from: sanFrancisco, using: .bfs())
// Shortest Paths - find optimal routes between cities
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: .dijkstra(weight: .property(\.weight)))
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: .aStar(weight: .property(\.weight), heuristic: { _, _ in 0 }))
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: .bellmanFord(weight: .property(\.weight)))
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: .bidirectionalDijkstra(weight: .property(\.weight)))
cityNetwork.shortestPaths(from: sanFrancisco, using: .spfa(weight: .property(\.weight)))
// K Shortest Paths - find multiple alternative routes
cityNetwork.kShortestPaths(from: sanFrancisco, to: denver, k: 3, using: .yen(weight: .property(\.weight)))
// Contraction Hierarchy - preprocess once for repeated fast queries on large graphs
let ch = cityNetwork.contractionHierarchy(weight: .property(\.weight))
ch.shortestPath(from: sanFrancisco, to: denver)
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: .contractionHierarchy(weight: .property(\.weight)))
// All-Pairs Shortest Paths - distances between all city pairs
cityNetwork.shortestPathsForAllPairs(using: .floydWarshall(weight: .property(\.weight)))
cityNetwork.shortestPathsForAllPairs(using: .johnson(edgeWeight: .property(\.weight)))
// Minimum Spanning Tree - minimum cost to connect all cities
cityNetwork.minimumSpanningTree(using: .kruskal(weight: .property(\.weight)))
cityNetwork.minimumSpanningTree(using: .prim(weight: .property(\.weight)))
cityNetwork.minimumSpanningTree(using: .boruvka(weight: .property(\.weight)))
// Maximum Flow - capacity/throughput analysis
cityNetwork.maximumFlow(from: sanFrancisco, to: denver, using: .fordFulkerson(capacityCost: .property(\.weight)))
cityNetwork.maximumFlow(from: sanFrancisco, to: denver, using: .edmondsKarp(capacityCost: .property(\.weight)))
cityNetwork.maximumFlow(from: sanFrancisco, to: denver, using: .dinic(capacityCost: .property(\.weight)))
// Minimum Cost Flow - cheapest way to route a given flow amount
cityNetwork.minimumCostFlow(from: sanFrancisco, to: denver, capacity: .property(\.weight), unitCost: .uniform(1.0))
cityNetwork.minimumCostFlow(from: sanFrancisco, to: denver, capacity: .property(\.capacity), unitCost: .property(\.cost), demand: 10.0)
cityNetwork.minimumCostFlow(from: sanFrancisco, to: denver, demand: nil, using: .successiveShortestPaths(capacity: .property(\.weight), unitCost: .uniform(1.0)))
// Minimum Cut - find the cheapest way to disconnect a graph
cityNetwork.minimumCut(weight: .property(\.weight))
cityNetwork.minimumCut(using: .stoerWagner(weight: .property(\.weight)))
// Connectivity & Components - analyze network structure
cityNetwork.connectedComponents()
cityNetwork.connectedComponents(using: .dfs())
cityNetwork.connectedComponents(using: .unionFind())
cityNetwork.stronglyConnectedComponents()
cityNetwork.stronglyConnectedComponents(using: .kosaraju())
cityNetwork.stronglyConnectedComponents(using: .tarjan())
// Articulation Points & Bridges - find critical vertices and edges
cityNetwork.articulationPoints()
cityNetwork.articulationPoints(using: .tarjan())
// Graph Coloring - assign colors to vertices with no adjacent same colors
cityNetwork.colorGraph()
cityNetwork.colorGraph(using: .greedy())
cityNetwork.colorGraph(using: .dsatur())
cityNetwork.colorGraph(using: .welshPowell())
// Topological Sort - order vertices respecting dependencies (DAGs)
cityNetwork.topologicalSort()
cityNetwork.topologicalSort(using: .dfs())
cityNetwork.topologicalSort(using: .kahn())
// Graph Properties - test structural characteristics
cityNetwork.isCyclic()
cityNetwork.isTree()
cityNetwork.isConnected()
cityNetwork.isBipartite()
// Eulerian Paths & Cycles - paths visiting every edge exactly once
cityNetwork.hasEulerianPath()
cityNetwork.hasEulerianCycle()
cityNetwork.eulerianPath()
cityNetwork.eulerianPath(using: .hierholzer())
cityNetwork.eulerianCycle()
cityNetwork.eulerianCycle(using: .hierholzer())
// Hamiltonian Paths & Cycles - paths visiting every vertex exactly once
cityNetwork.hamiltonianPath()
cityNetwork.hamiltonianPath(using: .backtracking())
cityNetwork.hamiltonianPath(using: .heuristic())
cityNetwork.hamiltonianPath(from: sanFrancisco)
cityNetwork.hamiltonianPath(from: sanFrancisco, to: denver)
cityNetwork.hamiltonianCycle()
cityNetwork.hamiltonianCycle(using: .backtracking())
cityNetwork.hamiltonianCycle(using: .heuristic())
// Clique Detection - find groups of fully connected vertices
cityNetwork.findCliques()
cityNetwork.findCliques(using: .bronKerbosch())
// Community Detection - identify clusters/groups in the network
cityNetwork.detectCommunities()
cityNetwork.detectCommunities(using: .louvain())
// Centrality Measures - measure vertex importance and influence
cityNetwork.centrality()
cityNetwork.centrality(using: .degree())
cityNetwork.centrality(using: .pageRank(dampingFactor: 0.85))
cityNetwork.centrality(using: .betweenness())
cityNetwork.centrality(using: .closeness())
cityNetwork.centrality(using: .eigenvector())
// Graph Isomorphism - check if two graphs have the same structure
var anotherNetwork = AdjacencyList()
// ... build another network
cityNetwork.isIsomorphic(to: anotherNetwork)
cityNetwork.isIsomorphic(to: anotherNetwork, using: .vf2())
cityNetwork.isIsomorphic(to: anotherNetwork, using: .weisfeilerLehman())
// Random Graph Generation - create synthetic networks
let randomNetwork = AdjacencyList.randomGraph(vertexCount: 50, using: .erdosRenyi(edgeProbability: 0.1))
let scaleFreeNetwork = AdjacencyList.randomGraph(vertexCount: 50, using: .barabasiAlbert(edgesPerVertex: 3))
let smallWorldNetwork = AdjacencyList.randomGraph(vertexCount: 50, using: .wattsStrogatz(neighbors: 4, rewiringProbability: 0.1))
// Graph Products - zero-copy lazy views combining two graphs
let cartesian = cityNetwork.cartesianProduct(with: anotherNetwork) // G □ H
let tensor = cityNetwork.tensorProduct(with: anotherNetwork) // G × H
let strong = cityNetwork.strongProduct(with: anotherNetwork) // G ⊠ H
let lexicographic = cityNetwork.lexicographicProduct(with: anotherNetwork) // G ∘ H
// Graph Families - zero-storage generator graphs
let complete = CompleteGraph(count: 5) // K₅: every pair connected
let path = PathGraph(n: 5) // P₅: 0→1→2→3→4
let cycle = CycleGraph(n: 6) // C₆: directed cycle
let star = StarGraph(n: 4) // S₄: hub + 4 leaves
let wheel = WheelGraph(n: 5) // W₅: hub connected to a cycle
let ladder = LadderGraph(n: 4) // 2×n grid with rungs
let hypercube = HypercubeGraph(n: 3) // Q₃: 8-vertex Boolean lattice
let completeBipartite = CompleteBipartiteGraph(m: 3, n: 4) // K₃,₄
let peterson = PetersonGraph() // the classic Petersen graphAdd to your Package.swift:
dependencies: [
.package(url: "https://github.com/tevelee/swift-graphs.git", from: "0.4.0")
]- AdjacencyList - Sparse graphs with property maps (most common)
- InlineGraph - Vertices and edges directly contain their data
- AdjacencyMatrix - Dense graphs, O(1) edge lookup
- GridGraph - 2D pathfinding and spatial algorithms
- LazyIncidenceGraph - Computed on-demand for large graphs
- BipartiteAdjacencyList - Two-colored graphs
Graph Families (zero-storage generator graphs):
- CompleteGraph - Complete graph K_n
- PathGraph - Directed path P_n
- CycleGraph - Directed cycle C_n
- StarGraph - Hub + n leaves S_n
- WheelGraph - Hub connected to a cycle W_n
- LadderGraph - 2×n ladder graph
- HypercubeGraph - Boolean hypercube Q_n
- CompleteBipartiteGraph - Complete bipartite K_{m,n}
- PetersonGraph - The classic Petersen graph
Shortest Paths - Dijkstra, Bidirectional Dijkstra, A*, Bellman-Ford, SPFA, Floyd-Warshall, Johnson, Yen (K-shortest), Contraction Hierarchy (fast repeated queries)
Traversal & Search - DFS (preorder/postorder), BFS, Best-First, Depth-Limited DFS, Iterative Deepening DFS
Connectivity - Connected Components (DFS, Union-Find), Strongly Connected Components (Tarjan, Kosaraju), Articulation Points & Bridges (Tarjan)
Graph Properties - Tree Detection, Cycle Detection, Bipartiteness, Connectivity, Eulerian Paths/Cycles, Hamiltonian Paths/Cycles (Backtracking, Heuristic)
Optimization - Minimum Spanning Tree (Kruskal, Prim, Borůvka), Maximum Flow (Ford-Fulkerson, Edmonds-Karp, Dinic), Minimum Cost Flow (Successive Shortest Paths), Minimum Cut (Stoer-Wagner), Graph Coloring (Greedy, DSatur, Welsh-Powell, Sequential), Matching (Hopcroft-Karp), Topological Sort (DFS, Kahn)
Advanced - Graph Isomorphism (VF2, Weisfeiler-Lehman), Clique Detection (Bron-Kerbosch), Community Detection (Louvain), Centrality Measures (PageRank, Betweenness, Closeness, Eigenvector, Degree), Random Graphs (Erdős-Rényi, Barabási-Albert, Watts-Strogatz)
Graph Views - Filtered, Reversed, Undirected, Complement, Graph Products (Cartesian, Tensor, Strong, Lexicographic)
Swift Graphs follows a protocol-oriented architecture inspired by the Boost Graph Library:
- Pluggable Components - Mix and match storage backends, property systems, and algorithms
- Type Safety - Compile-time constraints ensure correctness
- Performance - Zero-cost abstractions and optimized implementations
- Extensibility - Add custom graphs and algorithms without modifying existing code
- Modular Builds - SPM traits (Swift 6.2+) let you include only the algorithm families you need
Swift Graphs uses fine-grained protocols that compose together:
Core Protocols:
Graph (base: VertexDescriptor, EdgeDescriptor)
├── IncidenceGraph (outgoing edges, source/destination)
│ ├── BidirectionalGraph (+ incoming edges)
│ └── BinaryIncidenceGraph (left/right children for trees)
├── VertexListGraph (iterate all vertices)
├── EdgeListGraph (iterate all edges)
├── AdjacencyGraph (direct neighbor access)
└── EdgeLookupGraph (O(1) edge existence check)
Mutability Protocols:
Graph
├── VertexMutableGraph (add/remove vertices)
├── EdgeMutableGraph (add/remove edges)
└── MutableGraph (both vertices and edges)
└── MutableBinaryIncidenceGraph (binary tree mutation)
Property Protocols:
Graph
├── VertexPropertyGraph (vertex data)
├── EdgePropertyGraph (edge data)
└── PropertyGraph (both)
├── VertexMutablePropertyGraph (mutable vertex properties)
├── EdgeMutablePropertyGraph (mutable edge properties)
└── MutablePropertyGraph (fully mutable)
Specialized Protocols:
Graph
├── BipartiteGraph (two-colored graphs)
│ └── MutableBipartiteGraph (+ mutation)
└── Storage-backed (internal implementation helpers)
Algorithms work with any graph implementing the required protocols:
extension IncidenceGraph where Self: VertexListGraph, VertexDescriptor: Hashable {
func shortestPath<Weight: AdditiveArithmetic & Comparable>(
from source: VertexDescriptor,
to destination: VertexDescriptor,
using algorithm: some ShortestPathAlgorithm<Self, Weight>
) -> Path<VertexDescriptor, EdgeDescriptor>? {
// Works with ANY compatible graph
}
}Adding custom properties is straightforward with the type-safe property system:
// Define custom property types
enum Population: VertexProperty {
static let defaultValue = 0
}
enum IsCapital: VertexProperty {
static let defaultValue = false
}
enum Distance: EdgeProperty {
static let defaultValue = 0.0
}
// Extend property containers for convenient access
extension VertexPropertyValues {
var population: Int {
get { self[Population.self] }
set { self[Population.self] = newValue }
}
var isCapital: Bool {
get { self[IsCapital.self] }
set { self[IsCapital.self] = newValue }
}
}
extension EdgePropertyValues {
var distance: Double {
get { self[Distance.self] }
set { self[Distance.self] = newValue }
}
}
// Use your custom properties
var cities = AdjacencyList()
let sanFrancisco = cities.addVertex {
$0.population = 815201
$0.isCapital = false
}
let sacramento = cities.addVertex {
$0.population = 524943
$0.isCapital = true
}
cities.addEdge(from: sanFrancisco, to: sacramento) {
$0.distance = 87.0
}
// Query using custom properties
let capitals = cities.vertices().filter { cities[$0].isCapital }
let largeCities = cities.vertices().filter { cities[$0].population > 500000 }Graph products combine two graphs into a new one without copying any data. All four classical products are supported as lazy views:
let g = PathGraph(n: 3) // 0→1→2
let h = PathGraph(n: 3) // 0→1→2
// Cartesian product (G □ H): move in exactly one factor at a time
let cartesian = g.cartesianProduct(with: h)
// Vertices: (0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)
// (0,0)→(0,1) and (0,0)→(1,0) but not (0,0)→(1,1)
// Tensor product (G × H): move in both factors simultaneously
let tensor = g.tensorProduct(with: h)
// Strong product (G ⊠ H): move in one or both factors
let strong = g.strongProduct(with: h)
// Lexicographic product (G ∘ H): move in G, or stay in G and move in H
let lexicographic = g.lexicographicProduct(with: h)
// Products work with any graph and support traversal, shortest paths, etc.
let path = cartesian.shortestPath(from: Pair(0, 0), to: Pair(2, 2), using: .dijkstra())Contraction Hierarchy preprocesses a graph once for very fast repeated shortest-path queries. It significantly outperforms Dijkstra on large, road-network-style graphs.
// One-time preprocessing — build the hierarchy
let ch = cityNetwork.contractionHierarchy(weight: .property(\.weight))
// Many fast queries on the same hierarchy
let path1 = ch.shortestPath(from: sanFrancisco, to: denver)
let path2 = ch.shortestPath(from: losAngeles, to: phoenix)
// Alternatively, use the algorithm adapter for seamless integration
// (preprocessing happens lazily on the first call and is cached)
let lazyAlgorithm = ShortestPathAlgorithm.contractionHierarchy(weight: .property(\.weight))
cityNetwork.shortestPath(from: sanFrancisco, to: denver, using: lazyAlgorithm)
// Custom contraction order (optional)
let chOrdered = cityNetwork.contractionHierarchy(
weight: .property(\.weight),
vertexRank: { myRankFor($0) }
)Contributions are welcome!
# Clone and test
git clone https://github.com/tevelee/swift-graphs.git
cd swift-graphs
swift testMIT License - see LICENSE file for details.
- Inspired by the Boost Graph Library
- Built with Swift Collections and Swift Algorithms
Made with ❤️ for the Swift community