yograph 0.5.1
yograph: ^0.5.1 copied to clipboard
Graph algorithms and data structures for Dart: shortest paths, traversal, MST, flow, centrality, community detection, DAGs, and rendering.
Yograph đŗ #
āϝā§āĻ âĸ (jÅg) noun
- connection, link, union
- addition, sum
Yograph is a pure Dart graph algorithms and data structures library. It supports directed and undirected graphs, weighted graphs, traversal, shortest paths, minimum spanning trees, network flow, centrality, community detection, DAG algorithms, graph generators, I/O, and rendering.
Algorithms operate on capability-based interfaces, so custom graph representations can plug into the same APIs.
YogEx â Elixir implementation.
Yog â Gleam implementation.
Features #
Yograph includes algorithms for the following areas:
Pathfinding & Shortest Paths #
Dijkstra â Dijkstra.shortestPath(), Dijkstra.singleSourceDistances()
A* â AStar.aStar(), AStar.implicitAStar(), AStar.implicitAStarBy()
Bellman-Ford â BellmanFord.shortestPath(), negative cycle detection
Floyd-Warshall â FloydWarshall.allPairs(), all-pairs shortest paths
Johnson â Johnson.allPairs(), all-pairs shortest paths with negative weights (no negative cycles)
Bidirectional Dijkstra â BidirectionalDijkstra.shortestPath(), single-pair shortest path search
Bidirectional BFS â BidirectionalBfs.shortestPath(), fewest-edge single-pair search
Yen's K-Shortest â Yen.kShortestPaths(), up to [k] shortest loopless paths
Widest Path â Dijkstra.widestPath(), maximum bottleneck routing
Weighted algorithms support custom edge-weight semantics through WeightAlgebra<E>. Built-in algebras cover common numeric weights (double and int), while custom algebras let algorithms work with domain-specific edge data.
Traversal #
BFS & DFS â walk(), walkUntil(), foldWalk()
Best-First â bestFirstWalk(), bestFirstFold()
Topological Sort â topologicalSort(), lexicographicalTopologicalSort()
Random Walk â randomWalk() with optional seed
Connectivity & Structure #
Connected Components â Components.connectedComponents(), Components.weaklyConnectedComponents()
Strongly Connected Components â SCC.tarjan(), SCC.kosaraju()
Bridge & Articulation Point Detection â Analysis.analyze()
K-Core Decomposition â KCore.detect(), KCore.coreNumbers(), KCore.degeneracy()
Reachability Counts â Reachability.counts() with DAG and SCC condensation implementations
Structural Predicates â Structure.isTree(), Structure.isChordal(), Structure.isArborescence(), Structure.isComplete(), Structure.isRegular()
Centrality #
Degree â Centrality.degree() with DegreeMode (in / out / total)
Distance-Based â Centrality.closeness(), Centrality.harmonic(), Centrality.betweenness() (Brandes' algorithm)
Spectral / Iterative â Centrality.pageRank(), Centrality.eigenvector(), Centrality.katz(), Centrality.alpha()
Link-Analysis â Centrality.hits() (hub & authority scores)
Health Metrics #
Distance Metrics â Health.diameter(), Health.radius(), Health.eccentricity()
Structural â Health.assortativity() (Pearson degree correlation)
Path Metrics â Health.averagePathLength()
Efficiency â Health.globalEfficiency(), Health.localEfficiency(), Health.averageLocalEfficiency()
Minimum Spanning Tree #
Kruskal â MST.kruskal(), MST.kruskalMax()
Prim â MST.prim(), MST.primMax()
DAG Algorithms #
DAG Detection â DAG.isDag(), DAG.topologicalOrder()
Generations â DAG.topologicalGenerations()
Paths â DAG.longestPath(), DAG.shortestPath(), DAG.pathCount()
Distances â DAG.singleSourceDistances()
Sources & Sinks â DAG.sources(), DAG.sinks()
Ancestry â DAG.ancestors(), DAG.descendants(), DAG.lowestCommonAncestors()
Matching #
Bipartite Maximum Matching â Matching.hopcroftKarp()
Bipartite Minimum/Maximum Weight Perfect Matching â Matching.hungarian()
General Maximum Matching â Matching.blossomMaximumMatching() (Edmonds' blossom algorithm)
Community Detection #
Louvain â Community.louvain() (modularity optimization, hierarchical variant available)
Leiden â Community.leiden() (Louvain with refinement, hierarchical variant available)
Label Propagation â Community.labelPropagation()
Walktrap â Community.walktrap() (random-walk hierarchical clustering)
Metrics â Community.modularity(), Community.clusteringCoefficient(), Community.averageClusteringCoefficient(), Community.transitivity(), Community.countTriangles()
Utilities â Community.toMap(), Community.sizes(), Community.merge(), Community.nmi()
Graph Transformations #
Transitive Closure â Transform.transitiveClosure()
Transitive Reduction â Transform.transitiveReduction()
Design #
Capability-Based Interfaces â Traversable, Queryable, Mutable, Reversible combine into roles such as Walkable, WeightedWalkable, and Bidirectional.
Labeled Builder â LabeledBuilder maps string/enum labels to internal int node IDs.
Strategy Pattern â Pathfinding.shortestPath() accepts PointToPointStrategy implementations.
Weight Algebra â WeightAlgebra<E> lets weighted algorithms interpret custom edge data types.
Disjoint Set â DisjointSet with path compression and union by rank.
Installation #
Add yograph to your pubspec.yaml:
dependencies:
yograph: ^0.5.0
Or via command line:
dart pub add yograph
Quick Start #
import 'package:yograph/yograph.dart';
void main() {
// Create a directed graph with String labels on nodes, int weights on edges
final graph = SimpleGraph<String, int>.directed()
..addEdge(0, 1, data: 4)
..addEdge(0, 2, data: 2)
..addEdge(2, 1, data: 1)
..addEdge(1, 3, data: 5)
..addEdge(2, 3, data: 8);
// Find shortest path (default: Dijkstra)
final path = Pathfinding.shortestPath(graph, 0, 3);
print(path); // Path(0 -> 2 -> 1 -> 3, weight: 8)
// Use A* with a heuristic
final astarPath = Pathfinding.shortestPath(
graph,
0,
3,
strategy: AStar(heuristic: (node, goal) => (goal - node).abs().toDouble()),
);
print(astarPath); // Path(0 -> 2 -> 1 -> 3, weight: 8)
// Bellman-Ford handles negative weights
final bfGraph = SimpleGraph<String, int>.directed()
..addEdge(0, 1, data: 4)
..addEdge(0, 2, data: 3)
..addEdge(1, 2, data: -2)
..addEdge(2, 3, data: -3);
final result = BellmanFord.shortestPath(bfGraph, 0, 3);
if (result.isSuccess) {
print(result.path); // Path(0 -> 1 -> 2 -> 3, weight: -1)
} else if (result.hasNegativeCycle) {
print('Negative cycle detected!');
}
// All-pairs shortest paths
final fw = FloydWarshall.allPairs(graph);
print(fw.distance(0, 3)); // 8.0
// Minimum spanning tree (undirected only)
final undirected = SimpleGraph<String, int>.undirected()
..addEdge(0, 1, data: 4)
..addEdge(0, 2, data: 1)
..addEdge(1, 2, data: 3);
final mst = MST.kruskal(undirected);
print(mst.totalWeight); // 4
// Centrality
final scores = Centrality.betweenness(undirected);
print(scores); // {0: 0.0, 1: 0.0, 2: 0.0}
// Health metrics
print(Health.diameter(undirected)); // 1.0
print(Health.assortativity(undirected)); // 0.0 (all same degree)
// Connectivity
print(Components.connectedComponents(undirected)); // [[0, 1, 2]]
print(Structure.isTree(undirected)); // false (has a cycle)
}
Using Labels Instead of Integer IDs #
final builder = LabeledBuilder<String, int>.directed()
..addEdge('A', 'B', data: 4)
..addEdge('A', 'C', data: 2)
..addEdge('C', 'B', data: 1);
final graph = builder.toGraph();
final path = Pathfinding.shortestPath(graph, builder.getId('A')!, builder.getId('B')!);
print(path); // Path(0 -> 2 -> 1, weight: 3)
Connectivity & Structure #
final graph = SimpleGraph<String, void>.undirected()
..addEdge(0, 1)
..addEdge(1, 2)
..addEdge(2, 0)
..addEdge(0, 3);
// Bridges and articulation points
final analysis = Analysis.analyze(graph);
print(analysis.bridges); // [(0, 3)]
print(analysis.articulationPoints); // {0}
// Strongly connected components (directed)
final directed = SimpleGraph<String, void>.directed()
..addEdge(0, 1)
..addEdge(1, 2)
..addEdge(2, 0)
..addEdge(2, 3);
print(SCC.tarjan(directed)); // [[3], [0, 1, 2]]
// K-core decomposition
print(KCore.coreNumbers(graph)); // {0: 2, 1: 2, 2: 2, 3: 1}
// Structural predicates
print(Structure.isChordal(graph)); // true
print(Structure.isComplete(graph)); // false
DAG & Transforms #
final dag = SimpleGraph<String, void>.directed()
..addEdge(0, 1)
..addEdge(0, 2)
..addEdge(1, 3)
..addEdge(2, 3)
..addEdge(0, 3); // shortcut
// Topological generations
print(DAG.topologicalGenerations(dag)); // [[0], [1, 2], [3]]
// Longest path
print(DAG.longestPath(dag)); // [0, 1, 3] or [0, 2, 3]
// Transitive closure
print(Transform.transitiveClosure(dag));
// {0: {0, 1, 2, 3}, 1: {1, 3}, 2: {2, 3}, 3: {3}}
// Transitive reduction removes the redundant shortcut
final reduced = Transform.transitiveReduction(dag)!;
print(reduced.hasEdge(0, 3)); // false
Community Detection #
final social = SimpleGraph<String, int>.undirected()
..addEdge(0, 1, data: 1)
..addEdge(1, 2, data: 1)
..addEdge(2, 0, data: 1)
..addEdge(3, 4, data: 1)
..addEdge(4, 5, data: 1)
..addEdge(5, 3, data: 1)
..addEdge(2, 3, data: 1); // bridge between two cliques
// Louvain modularity optimization
final communities = Community.louvain(social, seed: 42);
print(communities.numCommunities); // 2
print(Community.modularity(social, communities)); // > 0.3
// Clustering metrics
print(Community.transitivity(social)); // global clustering coefficient
print(Community.averageClusteringCoefficient(social));
// Utilities
print(Community.toMap(communities)); // {0: {0, 1, 2}, 1: {3, 4, 5}}
Matching #
// Bipartite maximum cardinality
final bipartite = SimpleGraph<String, void>.undirected()
..addEdge(0, 3)
..addEdge(0, 4)
..addEdge(1, 4)
..addEdge(2, 5);
print(Matching.hopcroftKarp(bipartite)); // {0: 3, 1: 4, 2: 5}
// Weighted bipartite matching (Hungarian / Kuhn-Munkres)
final weighted = SimpleGraph<String, int>.undirected()
..addEdge(0, 2, data: 1)
..addEdge(0, 3, data: 4)
..addEdge(1, 2, data: 2)
..addEdge(1, 3, data: 3);
final result = Matching.hungarian(weighted, optimization: Optimization.min);
print(result.cost); // 4.0
print(result.matching); // {0: 2, 1: 3}
// General graph maximum matching (Edmonds' blossom)
final general = SimpleGraph<String, void>.undirected()
..addEdge(0, 1)
..addEdge(1, 2)
..addEdge(2, 0)
..addEdge(2, 3);
print(Matching.blossomMaximumMatching(general)); // {0: 1, 1: 0, 2: 3, 3: 2}
Development #
Running Tests #
# Full test suite
dart test
# With coverage
dart test --coverage=coverage
# Analyze
dart analyze --fatal-infos
# Format check
dart format --output=none --set-exit-if-changed .
Git Pre-Commit Hook #
This repository includes a Git pre-commit hook that formats staged .dart files and runs dart analyze before the commit.
To enable the pre-commit hook in your local clone, run:
git config core.hooksPath .githooks
License #
MIT