1 module dstruct.graph; 2 3 import std.algorithm; 4 import std.range; 5 import std.array; 6 import std.typecons; 7 8 import dstruct.support; 9 import dstruct.map; 10 11 /// The directionality of a graph. 12 enum EdgeDirection : bool { 13 /// Undirected, meaning edges face either direction. 14 undirected, 15 /// Directed, meaning edges face one direction. 16 directed, 17 } 18 19 @nogc @safe pure nothrow 20 private bool findAndRemove(T)(ref T[] arr, ref T needle) { 21 foreach(index; 0 .. arr.length) { 22 if (arr[index] == needle) { 23 foreach(newIndex; index .. arr.length - 1) { 24 arr[newIndex] = arr[newIndex + 1]; 25 } 26 27 arr = arr[0 .. $ - 1]; 28 return true; 29 } 30 } 31 32 return false; 33 } 34 35 @safe pure nothrow 36 private void addIfMissing(T)(ref T[] arr, ref T value) { 37 if (arr.countUntil(value) < 0) { 38 arr ~= value; 39 } 40 } 41 42 /** 43 * This struct represents a graph type as a reference type. 44 * 45 * Graphs types have a type of vertex and a direction. 46 * 47 * The graphs are represented by adjacency lists, which have good 48 * all-around performance characteristics for sparse graphs. 49 */ 50 struct BasicGraph(Vertex, EdgeDirection edgeDirection) { 51 private: 52 HashMap!(Vertex, Vertex[]) adjacencyMap; 53 public: 54 /// true if this graph is a directed graph. 55 enum bool isDirected = edgeDirection == EdgeDirection.directed; 56 57 /** 58 * Add a vertex to the graph. 59 */ 60 @safe pure nothrow 61 void addVertex(ref Vertex vertex) { 62 adjacencyMap.setDefault(vertex); 63 } 64 65 /// ditto 66 @safe pure nothrow 67 void addVertex(Vertex vertex) { 68 addVertex(vertex); 69 } 70 71 /** 72 * Remove the given vertex from this graph. 73 * 74 * Any edges to the given vertex will be removed. 75 * 76 * Returns: true if a vertex was removed. 77 */ 78 @nogc @safe pure nothrow 79 bool removeVertex(ref Vertex vertex) { 80 // Try to remove the vertex's adjacency mapping first. 81 if (!adjacencyMap.remove(vertex)) { 82 return false; 83 } 84 85 foreach(ref list; adjacencyMap.values) { 86 findAndRemove(list, vertex); 87 } 88 89 return true; 90 } 91 92 /// ditto 93 @nogc @safe pure nothrow 94 bool removeVertex(Vertex vertex) { 95 return removeVertex(vertex); 96 } 97 98 /** 99 * Returns: true if the vertex is in the graph. 100 */ 101 @nogc @safe pure nothrow 102 bool hasVertex(ref Vertex vertex) const { 103 return (vertex in adjacencyMap) !is null; 104 } 105 106 /// ditto 107 @nogc @safe pure nothrow 108 bool hasVertex(Vertex vertex) const { 109 return hasVertex(vertex); 110 } 111 112 /** 113 * Add an edge to the graph. 114 * 115 * New vertices will be added to the graph automatically. 116 */ 117 @safe pure nothrow 118 void addEdge(ref Vertex left, ref Vertex right) { 119 adjacencyMap.setDefault(left).addIfMissing(right); 120 121 static if (!isDirected) { 122 adjacencyMap.setDefault(right).addIfMissing(left); 123 } else { 124 addVertex(right); 125 } 126 } 127 128 /// ditto 129 @safe pure nothrow 130 void addEdge(ref Vertex left, Vertex right) { 131 addEdge(left, right); 132 } 133 134 /// ditto 135 @safe pure nothrow 136 void addEdge(Vertex left, ref Vertex right) { 137 addEdge(left, right); 138 } 139 140 /// ditto 141 @safe pure nothrow 142 void addEdge(Vertex left, Vertex right) { 143 addEdge(left, right); 144 } 145 146 /** 147 * Remove an edge from the graph. 148 * 149 * Vertices in the edge will not be removed. 150 * 151 * Returns: true if an edge was removed. 152 */ 153 @nogc @safe pure nothrow 154 bool removeEdge(ref Vertex left, ref Vertex right) { 155 auto listPtr = left in adjacencyMap; 156 157 if (listPtr is null) { 158 return false; 159 } 160 161 if (!findAndRemove(*listPtr, right)) { 162 return false; 163 } 164 165 static if (!isDirected) { 166 findAndRemove(adjacencyMap[right], left); 167 } 168 169 return true; 170 } 171 172 /// ditto 173 @nogc @safe pure nothrow 174 bool removeEdge(ref Vertex left, Vertex right) { 175 return removeEdge(left, right); 176 } 177 178 /// ditto 179 @nogc @safe pure nothrow 180 bool removeEdge(Vertex left, ref Vertex right) { 181 return removeEdge(left, right); 182 } 183 184 /// ditto 185 @nogc @safe pure nothrow 186 bool removeEdge(Vertex left, Vertex right) { 187 return removeEdge(left, right); 188 } 189 190 /** 191 * Check if an edge exists in the graph. 192 * 193 * Returns: true if the edge exists in the graph. 194 */ 195 @nogc @safe pure nothrow 196 bool hasEdge(ref Vertex left, ref Vertex right) const { 197 if (auto listPtr = left in adjacencyMap) { 198 return countUntil(*listPtr, right) > -1; 199 } 200 201 return false; 202 } 203 204 /// ditto 205 @nogc @safe pure nothrow 206 bool hasEdge(ref Vertex left, Vertex right) const { 207 return hasEdge(left, right); 208 } 209 210 /// ditto 211 @nogc @safe pure nothrow 212 bool hasEdge(Vertex left, ref Vertex right) const { 213 return hasEdge(left, right); 214 } 215 216 /// ditto 217 @nogc @safe pure nothrow 218 bool hasEdge(Vertex left, Vertex right) const { 219 return hasEdge(left, right); 220 } 221 222 /** 223 * Return the number of vertices in this graph 224 * in constant time. 225 * 226 * Returns: The number of vertices in this graph. 227 */ 228 @nogc @safe pure nothrow 229 @property 230 size_t vertexCount() const { 231 return adjacencyMap.length; 232 } 233 234 /** 235 * Return the number of directed edges in this graph 236 * in linear time. If this graph is a graph with undirected 237 * edges, this will always be double the undirected 238 * edge count. 239 * 240 * Returns: The number of directed edges in this graph. 241 */ 242 @nogc @safe pure nothrow 243 size_t directedEdgeCount() const { 244 size_t count = 0; 245 246 foreach(ref list; adjacencyMap.values()) { 247 count += list.length; 248 } 249 250 return count; 251 } 252 253 /** 254 * Return the number of edges in this graph 255 * in linear time. 256 * 257 * Returns: The number of edges in this graph. 258 */ 259 @nogc @safe pure nothrow 260 size_t edgeCount() const { 261 static if (!isDirected) { 262 return directedEdgeCount() / 2; 263 } else { 264 return directedEdgeCount(); 265 } 266 } 267 } 268 269 /** 270 * An edge in a graph. 271 */ 272 struct Edge(V) { 273 V from; 274 V to; 275 } 276 277 /// A shorthand for an undirected graph. 278 alias Graph(Vertex) = BasicGraph!(Vertex, EdgeDirection.undirected); 279 280 /// A shorthand for a directed graph. 281 alias Digraph(Vertex) = BasicGraph!(Vertex, EdgeDirection.directed); 282 283 /// Test if a type T is an undirected graph type with a particular vertex type. 284 enum isUndirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.undirected)); 285 286 /// Test if a type T is a directed graph type with a particular vertex type. 287 enum isDirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.directed)); 288 289 /// Test if a type T is any graph type with a particular vertex type. 290 enum isGraph(T, Vertex) = isUndirectedGraph!(T, Vertex) || isDirectedGraph!(T, Vertex); 291 292 // Test the templates. 293 unittest { 294 Graph!int graph; 295 296 assert(isUndirectedGraph!(typeof(graph), int)); 297 assert(isGraph!(typeof(graph), int)); 298 assert(!isGraph!(typeof(graph), short)); 299 300 Digraph!int digraph; 301 302 assert(isDirectedGraph!(typeof(digraph), int)); 303 assert(isGraph!(typeof(digraph), int)); 304 assert(!isGraph!(typeof(digraph), short)); 305 } 306 307 // Test adding vertices and the vertex count on graphs 308 unittest { 309 Graph!string graph; 310 311 foreach(symbol; ["a", "b", "c", "d", "a"]) { 312 graph.addVertex(symbol); 313 } 314 315 assert(graph.vertexCount == 4); 316 } 317 318 unittest { 319 Digraph!string digraph; 320 321 foreach(symbol; ["a", "b", "c", "d", "a"]) { 322 digraph.addVertex(symbol); 323 } 324 325 assert(digraph.vertexCount == 4); 326 } 327 328 // Test adding edges and the edge count. 329 unittest { 330 Graph!byte graph; 331 332 byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]]; 333 334 foreach(edge; edgeList) { 335 graph.addEdge(edge[0], edge[1]); 336 } 337 338 assert(graph.directedEdgeCount == 6); 339 assert(graph.edgeCount == 3); 340 assert(graph.hasVertex(1)); 341 } 342 343 // Test adding edges and the edge count. 344 unittest { 345 Digraph!byte graph; 346 347 byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]]; 348 349 foreach(edge; edgeList) { 350 graph.addEdge(edge[0], edge[1]); 351 } 352 353 assert(graph.directedEdgeCount == 4); 354 assert(graph.edgeCount == 4); 355 assert(graph.hasVertex(1)); 356 } 357 358 // Test adding one undirected graph edge implies the reverse. 359 unittest { 360 Graph!byte graph; 361 362 byte[2][] edgeList = [[1, 2], [3, 4]]; 363 364 foreach(edge; edgeList) { 365 graph.addEdge(edge[0], edge[1]); 366 } 367 368 assert(graph.edgeCount == 2); 369 assert(graph.hasEdge(2, 1)); 370 assert(graph.hasEdge(4, 3)); 371 } 372 373 // Test that removing a vertex also removes the edges. 374 unittest { 375 Digraph!byte graph; 376 377 byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]]; 378 379 foreach(edge; edgeList) { 380 graph.addEdge(edge[0], edge[1]); 381 } 382 383 assert(graph.removeVertex(1)); 384 assert(graph.edgeCount == 1); 385 assert(graph.hasEdge(2, 3)); 386 } 387 388 unittest { 389 Graph!byte graph; 390 391 byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]]; 392 393 foreach(edge; edgeList) { 394 graph.addEdge(edge[0], edge[1]); 395 } 396 397 assert(graph.removeVertex(1)); 398 assert(graph.edgeCount == 1); 399 assert(graph.hasEdge(2, 3)); 400 } 401 402 /** 403 * Given any type of graph, produce a range through the vertices of the graph. 404 * 405 * Params: 406 * graph = A graph. 407 * 408 * Returns: 409 * A ForwardRange through the vertices of the graph. 410 */ 411 @nogc @safe pure nothrow 412 auto vertices(V, EdgeDirection edgeDirection) 413 (auto ref BasicGraph!(V, edgeDirection) graph) { 414 return graph.adjacencyMap.keys; 415 } 416 417 /// ditto 418 @nogc @safe pure nothrow 419 auto vertices(V, EdgeDirection edgeDirection) 420 (auto ref const(BasicGraph!(V, edgeDirection)) graph) { 421 return graph.adjacencyMap.keys; 422 } 423 424 /// ditto 425 @nogc @safe pure nothrow 426 auto vertices(V, EdgeDirection edgeDirection) 427 (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) { 428 return graph.adjacencyMap.keys; 429 } 430 431 unittest { 432 Digraph!string graph; 433 434 graph.addEdge("a", "b"); 435 graph.addEdge("a", "c"); 436 graph.addEdge("a", "d"); 437 graph.addEdge("b", "e"); 438 graph.addEdge("b", "f"); 439 graph.addEdge("b", "g"); 440 441 string[] vertexList; 442 443 foreach(vertex; graph.vertices) { 444 vertexList ~= vertex; 445 } 446 447 // We know we will get this order from how the hashing works. 448 assert(vertexList == ["a", "b", "c", "d", "e", "f", "g"]); 449 } 450 451 unittest { 452 Graph!string mGraph; 453 const(Graph!string) cGraph; 454 immutable(Graph!string) iGraph; 455 456 auto mVertices = mGraph.vertices(); 457 auto cVertices = cGraph.vertices(); 458 auto iVertices = iGraph.vertices(); 459 460 assert(is(typeof(mVertices.front) == string)); 461 assert(is(typeof(cVertices.front) == const(string))); 462 assert(is(typeof(iVertices.front) == immutable(string))); 463 } 464 465 /** 466 * A range through the edges of a graph. 467 */ 468 struct EdgeRange(V, VArr) { 469 private: 470 ItemRange!(V, VArr) _itemRange; 471 size_t _outgoingIndex; 472 473 @nogc @safe pure nothrow 474 this (typeof(_itemRange) itemRange) { 475 _itemRange = itemRange; 476 477 // Advance until we find an edge. 478 while (!_itemRange.empty && _itemRange.front.value.length == 0) { 479 _itemRange.popFront; 480 } 481 } 482 public: 483 /// 484 @nogc @safe pure nothrow 485 inout(typeof(this)) save() inout { 486 return this; 487 } 488 489 /// 490 @nogc @safe pure nothrow 491 @property 492 bool empty() const { 493 return _itemRange.empty; 494 } 495 496 /// 497 @nogc @safe pure nothrow 498 @property 499 Edge!V front() const { 500 auto item = _itemRange.front; 501 502 return Edge!V(item.key, item.value[_outgoingIndex]); 503 } 504 505 /// 506 @nogc @safe pure nothrow 507 void popFront() { 508 if (++_outgoingIndex < _itemRange.front.value.length) { 509 // There's another outgoing edge in the list, so move to that. 510 return; 511 } 512 513 // We have to find the next vertex with a non-empty adjacency list. 514 _outgoingIndex = 0; 515 516 do { 517 _itemRange.popFront; 518 } while (!_itemRange.empty && _itemRange.front.value.length == 0); 519 } 520 } 521 522 /** 523 * Given any type of graph, produce a range through the edges of the graph. 524 * 525 * Params: 526 * graph = A graph. 527 * 528 * Returns: 529 * A ForwardRange through the edges of the graph. 530 */ 531 @nogc @safe pure nothrow 532 auto edges(V, EdgeDirection edgeDirection) 533 (auto ref BasicGraph!(V, edgeDirection) graph) { 534 return EdgeRange!(V, V[])(graph.adjacencyMap.items); 535 } 536 537 /// ditto 538 @nogc @trusted pure nothrow 539 auto edges(V, EdgeDirection edgeDirection) 540 (auto ref const(BasicGraph!(V, edgeDirection)) graph) { 541 return EdgeRange!(const(V), const(V[]))( 542 cast(ItemRange!(const(V), const(V[]))) 543 graph.adjacencyMap.items 544 ); 545 } 546 547 /// ditto 548 @nogc @trusted pure nothrow 549 auto edges(V, EdgeDirection edgeDirection) 550 (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) { 551 return EdgeRange!(immutable(V), immutable(V[]))( 552 cast(ItemRange!(immutable(V), immutable(V[]))) 553 graph.adjacencyMap.items 554 ); 555 } 556 557 unittest { 558 Digraph!string graph; 559 560 graph.addEdge("a", "b"); 561 graph.addEdge("a", "c"); 562 graph.addEdge("a", "d"); 563 graph.addEdge("b", "e"); 564 graph.addEdge("b", "f"); 565 graph.addEdge("b", "g"); 566 567 Edge!string[] edgeList; 568 569 foreach(edge; graph.edges) { 570 edgeList ~= edge; 571 } 572 573 // We know we will get this order from how the hashing works. 574 assert(edgeList.length); 575 assert(edgeList[0].from == "a"); 576 assert(edgeList[0].to == "b"); 577 assert(edgeList[1].from == "a"); 578 assert(edgeList[1].to == "c"); 579 assert(edgeList[2].from == "a"); 580 assert(edgeList[2].to == "d"); 581 assert(edgeList[3].from == "b"); 582 assert(edgeList[3].to == "e"); 583 assert(edgeList[4].from == "b"); 584 assert(edgeList[4].to == "f"); 585 assert(edgeList[5].from == "b"); 586 assert(edgeList[5].to == "g"); 587 } 588 589 unittest { 590 Graph!string mGraph; 591 const(Graph!string) cGraph; 592 immutable(Graph!string) iGraph; 593 594 auto mVertices = mGraph.edges(); 595 auto cVertices = cGraph.edges(); 596 auto iVertices = iGraph.edges(); 597 598 assert(is(typeof(mVertices.front) == Edge!string)); 599 assert(is(typeof(cVertices.front) == Edge!(const(string)))); 600 assert(is(typeof(iVertices.front) == Edge!(immutable(string)))); 601 }