1 // Written in the D programming language. 2 3 /** 4 Basic graph data structures. 5 6 Authors: $(LINK2 http://braingam.es/, Joseph Rushton Wakeling) 7 Copyright: Copyright © 2013 Joseph Rushton Wakeling 8 License: This program is free software: you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation, either version 3 of the License, or 11 (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program. If not, see $(LINK http://www.gnu.org/licenses/). 20 21 Credits: The basic graph data structure used here is adapted from the library 22 $(LINK2 http://igraph.sourceforge.net/, igraph) by Gábor Csárdi and 23 Tamás Nepusz. 24 */ 25 26 module dgraph.graph; 27 28 import std.algorithm, std.array, std.exception, std.range, std.traits; 29 import std..string : format; 30 31 /// Test if G is a Dgraph graph type. 32 template isGraph(G) 33 { 34 static if (!__traits(hasMember, G, "directed") || 35 !__traits(hasMember, G, "edge") || 36 !__traits(hasMember, G, "edgeCount") || 37 !__traits(hasMember, G, "vertexCount") || 38 !__traits(hasMember, G, "isEdge") || 39 !__traits(hasMember, G, "edgeID") || 40 !__traits(hasMember, G, "addEdge") || 41 !__traits(hasMember, G, "degreeIn") || 42 !__traits(hasMember, G, "degreeOut") || 43 !__traits(hasMember, G, "incidentEdgesIn") || 44 !__traits(hasMember, G, "incidentEdgesOut") || 45 !__traits(hasMember, G, "neighboursIn") || 46 !__traits(hasMember, G, "neighboursOut")) 47 { 48 enum bool isGraph = false; 49 } 50 else static if (!isBoolean!(typeof(G.directed))) 51 { 52 enum bool isGraph = false; 53 } 54 else static if (G.directed && (__traits(hasMember, G, "degree") || 55 __traits(hasMember, G, "incidentEdges") || 56 __traits(hasMember, G, "neighbours"))) 57 { 58 enum bool isGraph = false; 59 } 60 else static if (!G.directed && (!__traits(hasMember, G, "degree") || 61 !__traits(hasMember, G, "incidentEdges") || 62 !__traits(hasMember, G, "neighbours"))) 63 { 64 enum bool isGraph = false; 65 } 66 else static if (!isRandomAccessRange!(ReturnType!(G.incidentEdgesIn)) || 67 !isRandomAccessRange!(ReturnType!(G.incidentEdgesOut))) 68 { 69 enum bool isGraph = false; 70 } 71 else static if (!isRandomAccessRange!(ReturnType!(G.neighboursIn)) || 72 !isRandomAccessRange!(ReturnType!(G.neighboursOut))) 73 { 74 enum bool isGraph = false; 75 } 76 else static if (!G.directed && (!isRandomAccessRange!(ReturnType!(G.incidentEdges)) || 77 !isRandomAccessRange!(ReturnType!(G.neighbours)))) 78 { 79 enum bool isGraph = false; 80 } 81 else 82 { 83 enum bool isGraph = true; 84 } 85 } 86 87 /// Test if G is a directed graph. 88 template isDirectedGraph(G) 89 { 90 static if (isGraph!G) 91 { 92 enum bool isDirectedGraph = G.directed; 93 } 94 else 95 { 96 enum bool isDirectedGraph = false; 97 } 98 } 99 100 /// Test if G is an undirected graph. 101 template isUndirectedGraph(G) 102 { 103 static if (isGraph!G) 104 { 105 enum bool isUndirectedGraph = !G.directed; 106 } 107 else 108 { 109 enum bool isUndirectedGraph = false; 110 } 111 } 112 113 unittest 114 { 115 assert(isGraph!(IndexedEdgeList!true)); 116 assert(isGraph!(IndexedEdgeList!false)); 117 assert(isDirectedGraph!(IndexedEdgeList!true)); 118 assert(!isDirectedGraph!(IndexedEdgeList!false)); 119 assert(!isUndirectedGraph!(IndexedEdgeList!true)); 120 assert(isUndirectedGraph!(IndexedEdgeList!false)); 121 122 assert(isGraph!(CachedEdgeList!true)); 123 assert(isGraph!(CachedEdgeList!false)); 124 assert(isDirectedGraph!(CachedEdgeList!true)); 125 assert(!isDirectedGraph!(CachedEdgeList!false)); 126 assert(!isUndirectedGraph!(CachedEdgeList!true)); 127 assert(isUndirectedGraph!(CachedEdgeList!false)); 128 } 129 130 /** 131 * Graph data type based on igraph's igraph_t. The basic data structure is a 132 * pair of arrays whose entries consist of respectively the source (tail) and 133 * destination (head) vertices of the edges in the graph. These are extended 134 * by sorted indices and cumulative sums that enable fast calculation of graph 135 * properties from the stored data. 136 */ 137 final class IndexedEdgeList(bool dir) 138 { 139 private: 140 size_t[] tail_; 141 size_t[] head_; 142 size_t[] indexTail_; 143 size_t[] indexHead_; 144 size_t[] sumTail_ = [0]; 145 size_t[] sumHead_ = [0]; 146 147 void indexEdgesInsertion() 148 { 149 assert(indexTail_.length == indexHead_.length); 150 assert(tail_.length == head_.length); 151 immutable size_t l = indexTail_.length; 152 indexTail_.length = tail_.length; 153 indexHead_.length = head_.length; 154 foreach (immutable e; l .. tail_.length) 155 { 156 size_t i, j, lower, upper; 157 upper = indexTail_[0 .. e].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e] + 1).length; 158 lower = indexTail_[0 .. upper].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e]).length; 159 i = lower + indexTail_[lower .. upper].map!(a => head_[a]).assumeSorted.lowerBound(head_[e]).length; 160 for (j = e; j > i; --j) 161 { 162 indexTail_[j] = indexTail_[j - 1]; 163 } 164 indexTail_[i] = e; 165 166 upper = indexHead_[0 .. e].map!(a => head_[a]).assumeSorted.lowerBound(head_[e] + 1).length; 167 lower = indexHead_[0 .. upper].map!(a => head_[a]).assumeSorted.lowerBound(head_[e]).length; 168 i = lower + indexHead_[lower .. upper].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e]).length; 169 for (j = e; j > i; --j) 170 { 171 indexHead_[j] = indexHead_[j - 1]; 172 } 173 indexHead_[i] = e; 174 } 175 assert(indexTail_.length == indexHead_.length); 176 assert(indexTail_.length == tail_.length, format("%s tail indices but %s tail values.", indexTail_.length, tail_.length)); 177 assert(indexHead_.length == head_.length, format("%s head indices but %s head values.", indexHead_.length, head_.length)); 178 } 179 180 void indexEdgesSort() 181 { 182 indexTail_ ~= iota(indexTail_.length, tail_.length).array; 183 indexHead_ ~= iota(indexHead_.length, head_.length).array; 184 assert(indexTail_.length == indexHead_.length); 185 indexTail_.multiSort!((a, b) => tail_[a] < tail_[b], (a, b) => head_[a] < head_[b]); 186 indexHead_.multiSort!((a, b) => head_[a] < head_[b], (a, b) => tail_[a] < tail_[b]); 187 } 188 189 void sumEdges(ref size_t[] sum, in size_t[] vertex, in size_t[] index) @safe const nothrow pure 190 { 191 assert(sum.length > 1); 192 193 size_t v = vertex[index[0]]; 194 sum[0 .. v + 1] = 0; 195 for (size_t i = 1; i < index.length; ++i) 196 { 197 size_t n = vertex[index[i]] - vertex[index[sum[v]]]; 198 sum[v + 1 .. v + n + 1] = i; 199 v += n; 200 } 201 sum[v + 1 .. $] = vertex.length; 202 } 203 204 public: 205 /** 206 * Add new edges to the graph. These may be provided either singly, by 207 * passing an individual (tail, head) pair, or en masse by passing an array 208 * whose entries are [tail1, head1, tail2, head2, ...]. Duplicate edges 209 * are permitted. 210 */ 211 void addEdge()(size_t tail, size_t head) 212 { 213 enforce(tail < vertexCount, format("Edge tail %s is greater than largest vertex ID %s", tail, vertexCount - 1)); 214 enforce(head < vertexCount, format("Edge head %s is greater than largest vertex ID %s", head, vertexCount - 1)); 215 static if (!directed) 216 { 217 if (head < tail) 218 { 219 swap(tail, head); 220 } 221 } 222 tail_ ~= tail; 223 head_ ~= head; 224 indexEdgesInsertion(); 225 ++sumTail_[tail + 1 .. $]; 226 ++sumHead_[head + 1 .. $]; 227 } 228 229 /// ditto 230 void addEdge(T : size_t)(T[] edgeList) 231 { 232 enforce(edgeList.length % 2 == 0); 233 assert(tail_.length == head_.length); 234 immutable size_t l = tail_.length; 235 tail_.length += edgeList.length / 2; 236 head_.length += edgeList.length / 2; 237 foreach (immutable i; 0 .. edgeList.length / 2) 238 { 239 size_t tail = edgeList[2 * i]; 240 size_t head = edgeList[2 * i + 1]; 241 enforce(tail < vertexCount, format("Edge tail %s is greater than largest vertex ID %s", tail, vertexCount - 1)); 242 enforce(head < vertexCount, format("Edge head %s is greater than vertex count %s", head, vertexCount - 1)); 243 static if (!directed) 244 { 245 if (head < tail) 246 { 247 swap(tail, head); 248 } 249 } 250 tail_[l + i] = tail; 251 head_[l + i] = head; 252 } 253 indexEdgesSort(); 254 sumEdges(sumTail_, tail_, indexTail_); 255 sumEdges(sumHead_, head_, indexHead_); 256 } 257 258 static if (directed) 259 { 260 /** 261 * Provide respectively the in- and out-degrees of a vertex v, i.e. 262 * the number of vertices to which v is connected by respectively 263 * incoming or outgoing links. If the graph is undirected, these 264 * values are identical and the general degree method is also defined. 265 */ 266 size_t degreeIn(in size_t v) @safe const pure 267 { 268 enforce(isVertex(v)); 269 assert(v + 1 < sumHead_.length); 270 return sumHead_[v + 1] - sumHead_[v]; 271 } 272 273 ///ditto 274 size_t degreeOut(in size_t v) @safe const pure 275 { 276 enforce(isVertex(v)); 277 assert(v + 1 < sumTail_.length); 278 return sumTail_[v + 1] - sumTail_[v]; 279 } 280 } 281 else 282 { 283 /// Provides the degree of a vertex v in an undirected graph. 284 size_t degree(in size_t v) @safe const pure 285 { 286 enforce(isVertex(v)); 287 assert(v + 1 < sumTail_.length); 288 assert(sumTail_.length == sumHead_.length); 289 return (sumTail_[v + 1] - sumTail_[v]) 290 + (sumHead_[v + 1] - sumHead_[v]); 291 } 292 293 alias degreeIn = degree; 294 alias degreeOut = degree; 295 } 296 297 /** 298 * Static boolean value indicating whether or not the graph is directed. 299 * This is available for compile-time as well as runtime checks. 300 */ 301 alias directed = dir; 302 303 /** 304 * Returns a list of all edges in the graph in the form of a list of 305 * (tail, head) vertex pairs. 306 */ 307 auto edge() @property @safe const nothrow pure 308 { 309 return zip(tail_, head_); 310 } 311 312 /// Total number of edges in the graph. 313 size_t edgeCount() @property @safe const nothrow pure 314 { 315 assert(tail_.length == head_.length); 316 return tail_.length; 317 } 318 319 /** 320 * Returns the edge index for a given (tail, head) vertex pair. If 321 * (tail, head) is not an edge, will throw an exception. 322 */ 323 size_t edgeID(size_t tail, size_t head) const 324 { 325 if (!isVertex(tail)) 326 { 327 throw new Exception(format("No vertex with ID %s", tail)); 328 } 329 330 if (!isVertex(head)) 331 { 332 throw new Exception(format("No vertex with ID %s", head)); 333 } 334 335 static if (!directed) 336 { 337 if (head < tail) 338 { 339 swap(tail, head); 340 } 341 } 342 343 size_t tailDeg = sumTail_[tail + 1] - sumTail_[tail]; 344 size_t headDeg = sumHead_[head + 1] - sumHead_[head]; 345 346 if (tailDeg == 0) 347 { 348 static if (directed) 349 { 350 assert(degreeOut(tail) == 0); 351 throw new Exception(format("Vertex %s has no outgoing neighbours.", tail)); 352 } 353 else 354 { 355 throw new Exception(format("(%s, %s) is not an edge", tail, head)); 356 } 357 } 358 359 if (headDeg == 0) 360 { 361 static if (directed) 362 { 363 assert(degreeIn(head) == 0); 364 throw new Exception(format("Vertex %s has no incoming neighbours.", head)); 365 } 366 else 367 { 368 throw new Exception(format("(%s, %s) is not an edge", tail, head)); 369 } 370 } 371 372 if (tailDeg < headDeg) 373 { 374 // search among the heads of tail 375 foreach (immutable i; iota(sumTail_[tail], sumTail_[tail + 1]).map!(a => indexTail_[a])) 376 { 377 if (head_[i] == head) 378 { 379 assert(tail_[i] == tail); 380 return i; 381 } 382 } 383 throw new Exception(format("(%s, %s) is not an edge.", tail, head)); 384 } 385 else 386 { 387 // search among the tails of head 388 foreach (immutable i; iota(sumHead_[head], sumHead_[head + 1]).map!(a => indexHead_[a])) 389 { 390 if (tail_[i] == tail) 391 { 392 assert(head_[i] == head); 393 return i; 394 } 395 } 396 throw new Exception(format("(%s, %s) is not an edge.", tail, head)); 397 } 398 } 399 400 static if (directed) 401 { 402 /** 403 * Returns the IDs of edges respectively incoming to or outgoing from 404 * the specified vertex v. If the graph is undirected the two will be 405 * identical and the general method incidentEdges is also defined. 406 */ 407 auto incidentEdgesIn(in size_t v) const 408 { 409 enforce(isVertex(v)); 410 return iota(sumHead_[v], sumHead_[v + 1]).map!(a => indexHead_[a]); 411 } 412 413 /// ditto 414 auto incidentEdgesOut(in size_t v) const 415 { 416 enforce(isVertex(v)); 417 return iota(sumTail_[v], sumTail_[v + 1]).map!(a => indexTail_[a]); 418 } 419 } 420 else 421 { 422 /// ditto 423 auto incidentEdges(in size_t v) const 424 { 425 enforce(isVertex(v)); 426 return chain(iota(sumHead_[v], sumHead_[v + 1]).map!(a => indexHead_[a]), 427 iota(sumTail_[v], sumTail_[v + 1]).map!(a => indexTail_[a])); 428 } 429 430 alias incidentEdgesIn = incidentEdges; 431 alias incidentEdgesOut = incidentEdges; 432 } 433 434 /** 435 * Checks if a given (tail, head) vertex pair forms an edge in the graph. 436 */ 437 bool isEdge(size_t tail, size_t head) const 438 { 439 if (!(isVertex(tail) && isVertex(head))) 440 { 441 return false; 442 } 443 444 static if (!directed) 445 { 446 if (head < tail) 447 { 448 swap(tail, head); 449 } 450 } 451 452 size_t tailDeg = sumTail_[tail + 1] - sumTail_[tail]; 453 if (tailDeg == 0) 454 { 455 return false; 456 } 457 458 size_t headDeg = sumHead_[head + 1] - sumHead_[head]; 459 if (headDeg == 0) 460 { 461 return false; 462 } 463 464 if (tailDeg < headDeg) 465 { 466 // search among the heads of tail 467 foreach (immutable t; iota(sumTail_[tail], sumTail_[tail + 1]).map!(a => head_[indexTail_[a]])) 468 { 469 if (t == head) 470 { 471 return true; 472 } 473 } 474 return false; 475 } 476 else 477 { 478 // search among the tails of head 479 foreach (immutable h; iota(sumHead_[head], sumHead_[head + 1]).map!(a => tail_[indexHead_[a]])) 480 { 481 if (h == tail) 482 { 483 return true; 484 } 485 } 486 return false; 487 } 488 } 489 490 bool isVertex(T : size_t)(in T v) @safe const nothrow pure 491 { 492 static if (isSigned!T) 493 { 494 if (v < 0) 495 { 496 return false; 497 } 498 } 499 500 if (v < vertexCount) 501 { 502 return true; 503 } 504 505 return false; 506 } 507 508 static if (directed) 509 { 510 /** 511 * Returns the IDs of vertices connected to v via incoming or outgoing 512 * links. If the graph is undirected the two will be identical and the 513 * general neighbours method is also defined. 514 */ 515 auto neighboursIn(in size_t v) const 516 { 517 enforce(isVertex(v)); 518 return iota(sumHead_[v], sumHead_[v + 1]).map!(a => tail_[indexHead_[a]]); 519 } 520 521 /// ditto 522 auto neighboursOut(in size_t v) const 523 { 524 enforce(isVertex(v)); 525 return iota(sumTail_[v], sumTail_[v + 1]).map!(a => head_[indexTail_[a]]); 526 } 527 } 528 else 529 { 530 /// ditto 531 auto neighbours(in size_t v) const 532 { 533 enforce(isVertex(v)); 534 return chain(iota(sumHead_[v], sumHead_[v + 1]).map!(a => tail_[indexHead_[a]]), 535 iota(sumTail_[v], sumTail_[v + 1]).map!(a => head_[indexTail_[a]])); 536 } 537 538 alias neighbors = neighbours; 539 alias neighboursIn = neighbours; 540 alias neighboursOut = neighbours; 541 } 542 543 alias neighborsIn = neighboursIn; 544 alias neighborsOut = neighboursOut; 545 546 /** 547 * Get or set the total number of vertices in the graph. Will throw an 548 * exception if resetting the number of vertices would delete edges. 549 */ 550 size_t vertexCount() @property @safe const nothrow pure 551 { 552 assert(sumTail_.length == sumHead_.length); 553 return sumTail_.length - 1; 554 } 555 556 /// ditto 557 size_t vertexCount(in size_t n) @property @safe pure 558 { 559 immutable size_t l = sumTail_.length; 560 if (n < (l - 1)) 561 { 562 // Check that no edges are lost this way 563 if ((sumTail_[n] != sumTail_[$-1]) || 564 (sumHead_[n] != sumHead_[$-1])) 565 { 566 throw new Exception("Cannot set vertexCount value without deleting edges"); 567 } 568 else 569 { 570 sumTail_.length = n + 1; 571 sumHead_.length = n + 1; 572 } 573 } 574 else 575 { 576 sumTail_.length = n + 1; 577 sumHead_.length = n + 1; 578 sumTail_[l .. $] = sumTail_[l - 1]; 579 sumHead_[l .. $] = sumHead_[l - 1]; 580 } 581 return vertexCount; 582 } 583 } 584 585 /** 586 * An extension of IndexedEdgeList that caches the results of calculations of 587 * various graph properties so as to provide speedier performance. Provides 588 * the same set of public methods. This is the recommended data type to use 589 * with Dgraph. 590 */ 591 final class CachedEdgeList(bool dir) 592 { 593 private: 594 IndexedEdgeList!dir graph_; 595 size_t[] incidentEdgesCache_; 596 size_t[] neighboursCache_; 597 598 static if (directed) 599 { 600 const(size_t)[][] incidentEdgesIn_; 601 const(size_t)[][] incidentEdgesOut_; 602 const(size_t)[][] neighboursIn_; 603 const(size_t)[][] neighboursOut_; 604 } 605 else 606 { 607 const(size_t)[][] incidentEdges_; 608 const(size_t)[][] neighbours_; 609 } 610 611 public: 612 this() 613 { 614 graph_ = new IndexedEdgeList!dir; 615 } 616 617 alias graph_ this; 618 619 void addEdge()(size_t tail, size_t head) 620 { 621 graph_.addEdge(tail, head); 622 neighboursCache_.length = 2 * tail_.length; 623 incidentEdgesCache_.length = 2 * tail_.length; 624 static if (directed) 625 { 626 neighboursIn_[] = null; 627 neighboursOut_[] = null; 628 incidentEdgesIn_[] = null; 629 incidentEdgesOut_[] = null; 630 } 631 else 632 { 633 neighbours_[] = null; 634 incidentEdges_[] = null; 635 } 636 } 637 638 void addEdge(T : size_t)(T[] edgeList) 639 { 640 graph_.addEdge(edgeList); 641 neighboursCache_.length = 2 * tail_.length; 642 incidentEdgesCache_.length = 2 * tail_.length; 643 static if (directed) 644 { 645 neighboursIn_[] = null; 646 neighboursOut_[] = null; 647 incidentEdgesIn_[] = null; 648 incidentEdgesOut_[] = null; 649 } 650 else 651 { 652 neighbours_[] = null; 653 incidentEdges_[] = null; 654 } 655 } 656 657 static if (directed) 658 { 659 size_t degreeIn(in size_t v) @safe const pure 660 { 661 return graph_.degreeIn(v); 662 } 663 664 size_t degreeOut(in size_t v) @safe const pure 665 { 666 return graph_.degreeOut(v); 667 } 668 } 669 else 670 { 671 size_t degree(in size_t v) @safe const pure 672 { 673 return graph_.degree(v); 674 } 675 676 alias degreeIn = degree; 677 alias degreeOut = degree; 678 } 679 680 alias directed = dir; 681 682 auto edge() @property @safe const nothrow pure 683 { 684 return graph_.edge; 685 } 686 687 size_t edgeCount() @property @safe const nothrow pure 688 { 689 return graph_.edgeCount; 690 } 691 692 size_t edgeID(size_t tail, size_t head) const 693 { 694 return graph_.edgeID(tail, head); 695 } 696 697 static if (directed) 698 { 699 auto incidentEdgesIn(in size_t v) @safe nothrow pure 700 { 701 if (incidentEdgesIn_[v] is null) 702 { 703 immutable size_t start = sumHead_[v] + sumTail_[v]; 704 immutable size_t end = sumTail_[v] + sumHead_[v + 1]; 705 size_t j = start; 706 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1]) 707 { 708 incidentEdgesCache_[j] = indexHead_[i]; 709 ++j; 710 } 711 assert(j == end); 712 incidentEdgesIn_[v] = incidentEdgesCache_[start .. end]; 713 } 714 return incidentEdgesIn_[v]; 715 } 716 717 auto incidentEdgesOut(in size_t v) @safe nothrow pure 718 { 719 if (incidentEdgesOut_[v] is null) 720 { 721 immutable size_t start = sumTail_[v] + sumHead_[v + 1]; 722 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1]; 723 size_t j = start; 724 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1]) 725 { 726 incidentEdgesCache_[j] = indexTail_[i]; 727 ++j; 728 } 729 assert(j == end); 730 incidentEdgesOut_[v] = incidentEdgesCache_[start .. end]; 731 } 732 return incidentEdgesOut_[v]; 733 } 734 } 735 else 736 { 737 auto incidentEdges(in size_t v) @safe nothrow pure 738 { 739 if (incidentEdges_[v] is null) 740 { 741 immutable size_t start = sumHead_[v] + sumTail_[v]; 742 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1]; 743 size_t j = start; 744 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1]) 745 { 746 incidentEdgesCache_[j] = indexHead_[i]; 747 ++j; 748 } 749 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1]) 750 { 751 incidentEdgesCache_[j] = indexTail_[i]; 752 ++j; 753 } 754 assert(j == end); 755 incidentEdges_[v] = incidentEdgesCache_[start .. end]; 756 } 757 return incidentEdges_[v]; 758 } 759 760 alias incidentEdgesIn = incidentEdges; 761 alias incidentEdgesOut = incidentEdges; 762 } 763 764 bool isEdge(size_t tail, size_t head) const 765 { 766 return graph_.isEdge(tail, head); 767 } 768 769 bool isVertex(T : size_t)(in T v) @safe const nothrow pure 770 { 771 return graph_.isVertex(v); 772 } 773 774 static if (directed) 775 { 776 auto neighboursIn(in size_t v) @safe nothrow pure 777 { 778 if (neighboursIn_[v] is null) 779 { 780 immutable size_t start = sumHead_[v] + sumTail_[v]; 781 immutable size_t end = sumTail_[v] + sumHead_[v + 1]; 782 size_t j = start; 783 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1]) 784 { 785 neighboursCache_[j] = tail_[indexHead_[i]]; 786 ++j; 787 } 788 assert(j == end); 789 neighboursIn_[v] = neighboursCache_[start .. end]; 790 } 791 return neighboursIn_[v]; 792 } 793 794 auto neighboursOut(in size_t v) @safe nothrow pure 795 { 796 if (neighboursOut_[v] is null) 797 { 798 immutable size_t start = sumTail_[v] + sumHead_[v + 1]; 799 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1]; 800 size_t j = start; 801 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1]) 802 { 803 neighboursCache_[j] = head_[indexTail_[i]]; 804 ++j; 805 } 806 assert(j == end); 807 neighboursOut_[v] = neighboursCache_[start .. end]; 808 } 809 return neighboursOut_[v]; 810 } 811 } 812 else 813 { 814 auto neighbours(in size_t v) @safe nothrow pure 815 { 816 if (neighbours_[v] is null) 817 { 818 immutable size_t start = sumHead_[v] + sumTail_[v]; 819 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1]; 820 size_t j = start; 821 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1]) 822 { 823 neighboursCache_[j] = tail_[indexHead_[i]]; 824 ++j; 825 } 826 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1]) 827 { 828 neighboursCache_[j] = head_[indexTail_[i]]; 829 ++j; 830 } 831 assert(j == end); 832 neighbours_[v] = neighboursCache_[start .. end]; 833 } 834 return neighbours_[v]; 835 } 836 837 alias neighbors = neighbours; 838 alias neighboursIn = neighbours; 839 alias neighboursOut = neighbours; 840 } 841 842 alias neighborsIn = neighboursIn; 843 alias neighborsOut = neighboursOut; 844 845 size_t vertexCount() @property @safe const nothrow pure 846 { 847 return graph_.vertexCount; 848 } 849 850 size_t vertexCount(in size_t n) @property @safe pure 851 { 852 static if (directed) 853 { 854 assert(sumHead_.length == neighboursIn_.length + 1); 855 assert(sumTail_.length == neighboursOut_.length + 1); 856 assert(sumHead_.length == incidentEdgesIn_.length + 1); 857 assert(sumTail_.length == incidentEdgesOut_.length + 1); 858 } 859 else 860 { 861 assert(sumTail_.length == neighbours_.length + 1); 862 assert(sumHead_.length == incidentEdges_.length + 1); 863 } 864 865 immutable size_t l = sumTail_.length; 866 graph_.vertexCount = n; 867 868 static if (directed) 869 { 870 neighboursIn_.length = n; 871 neighboursOut_.length = n; 872 incidentEdgesIn_.length = n; 873 incidentEdgesOut_.length = n; 874 875 if (n >= l) 876 { 877 neighboursIn_[l - 1 .. $] = null; 878 neighboursOut_[l - 1 .. $] = null; 879 incidentEdgesIn_[l - 1 .. $] = null; 880 incidentEdgesOut_[l - 1 .. $] = null; 881 } 882 } 883 else 884 { 885 neighbours_.length = n; 886 incidentEdges_.length = n; 887 888 if (n >= l) 889 { 890 neighbours_[l - 1 .. $] = null; 891 incidentEdges_[l - 1 .. $] = null; 892 } 893 } 894 return vertexCount; 895 } 896 } 897 898 unittest 899 { 900 import std.typetuple; 901 902 foreach (Graph; TypeTuple!(IndexedEdgeList, CachedEdgeList)) 903 { 904 foreach (directed; TypeTuple!(true, false)) 905 { 906 /* We begin by creating two graphs with the different 907 * addEdge methods and ensuring that they wind up with 908 * the same internal data. 909 */ 910 auto g1 = new Graph!directed; 911 auto g2 = new Graph!directed; 912 g1.vertexCount = g2.vertexCount = 10; 913 assert(g1.vertexCount == 10); 914 assert(g2.vertexCount == 10); 915 916 foreach (v; 0 .. 10) 917 { 918 assert(g1.isVertex(v), format("%s should be a vertex!")); 919 } 920 921 foreach (v; 10 .. 20) 922 { 923 assert(!g1.isVertex(v), format("%s should not be a vertex!")); 924 } 925 926 g1.addEdge(5, 8); 927 g1.addEdge(5, 4); 928 g1.addEdge(7, 4); 929 g1.addEdge(3, 4); 930 g1.addEdge(6, 9); 931 g1.addEdge(3, 2); 932 933 g2.addEdge([5, 8, 5, 4, 7, 4, 3, 4, 6, 9, 3, 2]); 934 935 assert(g1.edgeCount == 6); 936 assert(g2.edgeCount == 6); 937 938 if (directed) 939 { 940 assert(g1.tail_ == [5, 5, 7, 3, 6, 3]); 941 assert(g2.tail_ == [5, 5, 7, 3, 6, 3]); 942 assert(g1.head_ == [8, 4, 4, 4, 9, 2]); 943 assert(g2.head_ == [8, 4, 4, 4, 9, 2]); 944 945 assert(g1.indexTail_ == [5, 3, 1, 0, 4, 2]); 946 assert(g2.indexTail_ == [5, 3, 1, 0, 4, 2]); 947 assert(g1.indexHead_ == [5, 3, 1, 2, 0, 4]); 948 assert(g2.indexHead_ == [5, 3, 1, 2, 0, 4]); 949 950 assert(g1.sumTail_ == [0, 0, 0, 0, 2, 2, 4, 5, 6, 6, 6]); 951 assert(g2.sumTail_ == [0, 0, 0, 0, 2, 2, 4, 5, 6, 6, 6]); 952 assert(g1.sumHead_ == [0, 0, 0, 1, 1, 4, 4, 4, 4, 5, 6]); 953 assert(g2.sumHead_ == [0, 0, 0, 1, 1, 4, 4, 4, 4, 5, 6]); 954 } 955 else 956 { 957 assert(g1.tail_ == [5, 4, 4, 3, 6, 2]); 958 assert(g2.tail_ == [5, 4, 4, 3, 6, 2]); 959 assert(g1.head_ == [8, 5, 7, 4, 9, 3]); 960 assert(g2.head_ == [8, 5, 7, 4, 9, 3]); 961 962 assert(g1.indexTail_ == [5, 3, 1, 2, 0, 4]); 963 assert(g2.indexTail_ == [5, 3, 1, 2, 0, 4]); 964 assert(g1.indexHead_ == [5, 3, 1, 2, 0, 4]); 965 assert(g2.indexHead_ == [5, 3, 1, 2, 0, 4]); 966 967 assert(g1.sumTail_ == [0, 0, 0, 1, 2, 4, 5, 6, 6, 6, 6]); 968 assert(g2.sumTail_ == [0, 0, 0, 1, 2, 4, 5, 6, 6, 6, 6]); 969 assert(g1.sumHead_ == [0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 6]); 970 assert(g2.sumHead_ == [0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 6]); 971 } 972 973 foreach (immutable v; 0 .. g1.vertexCount) 974 { 975 assert(g1.degreeIn(v) == g2.degreeIn(v)); 976 assert(g1.degreeOut(v) == g2.degreeOut(v)); 977 static if (!directed) 978 { 979 assert(g1.degree(v) == g2.degree(v)); 980 } 981 } 982 983 /* If the above all passes, then we know that the internal data 984 * representation is correct and we can focus on how the graph 985 * transforms this into output. 986 * 987 * Let's start by checking that vertex IDs are recognized ... 988 */ 989 foreach (immutable i; -10 .. 20) 990 { 991 if (0 <= i && i < g1.vertexCount) 992 { 993 assert(g1.isVertex(i)); 994 } 995 else 996 { 997 assert(!g1.isVertex(i)); 998 } 999 } 1000 1001 /* Now check that edges are correctly recognized and that the 1002 * edge ID representation works. 1003 */ 1004 foreach (immutable h; -10 .. 20) 1005 { 1006 foreach (immutable t; -10 .. 20) 1007 { 1008 if (directed) 1009 { 1010 if ((h == 5 && t == 8) || 1011 (h == 5 && t == 4) || 1012 (h == 7 && t == 4) || 1013 (h == 3 && t == 4) || 1014 (h == 6 && t == 9) || 1015 (h == 3 && t == 2)) 1016 { 1017 assert(g1.isEdge(h, t)); 1018 auto i = g1.edgeID(h, t); 1019 assert(h == g1.tail_[i]); 1020 assert(t == g1.head_[i]); 1021 } 1022 else 1023 { 1024 assert(!g1.isEdge(h, t)); 1025 assertThrown(g1.edgeID(h, t)); 1026 } 1027 } 1028 else 1029 { 1030 if ((h == 5 && t == 8) || (h == 8 && t == 5) || 1031 (h == 5 && t == 4) || (h == 4 && t == 5) || 1032 (h == 7 && t == 4) || (h == 4 && t == 7) || 1033 (h == 3 && t == 4) || (h == 4 && t == 3) || 1034 (h == 6 && t == 9) || (h == 9 && t == 6) || 1035 (h == 3 && t == 2) || (h == 2 && t == 3)) 1036 { 1037 assert(g1.isEdge(h, t)); 1038 auto i = g1.edgeID(h, t); 1039 if (h <= t) 1040 { 1041 assert(h == g1.tail_[i]); 1042 assert(t == g1.head_[i]); 1043 } 1044 else 1045 { 1046 assert(h == g1.head_[i]); 1047 assert(t == g1.tail_[i]); 1048 } 1049 } 1050 else 1051 { 1052 assert(!g1.isEdge(h, t)); 1053 assertThrown(g1.edgeID(h, t)); 1054 } 1055 } 1056 } 1057 } 1058 1059 // Another check on edge ID. 1060 foreach (immutable e; 0 .. g1.edgeCount) 1061 { 1062 size_t h = g1.tail_[e]; 1063 size_t t = g1.head_[e]; 1064 assert(e == g1.edgeID(h, t)); 1065 if (!directed) 1066 { 1067 assert(e == g1.edgeID(t, h)); 1068 } 1069 } 1070 1071 // Let's check the edge and neighbour functions. 1072 foreach (immutable v; 0 .. g1.vertexCount) 1073 { 1074 foreach (immutable e, immutable n; zip(g1.incidentEdgesOut(v), g1.neighboursOut(v))) 1075 { 1076 if (directed || v <= n) 1077 { 1078 assert(g1.edge[e][0] == v); 1079 assert(g1.edge[e][1] == n); 1080 } 1081 else 1082 { 1083 assert(g1.edge[e][0] == n); 1084 assert(g1.edge[e][1] == v); 1085 } 1086 } 1087 1088 foreach (immutable e, immutable n; zip(g1.incidentEdgesIn(v), g1.neighboursIn(v))) 1089 { 1090 if (directed || v > n) 1091 { 1092 assert(g1.edge[e][0] == n); 1093 assert(g1.edge[e][1] == v); 1094 } 1095 else 1096 { 1097 assert(g1.edge[e][0] == v); 1098 assert(g1.edge[e][1] == n); 1099 } 1100 } 1101 1102 static if (!directed) 1103 { 1104 foreach (immutable e, immutable n; zip(g1.incidentEdges(v), g1.neighbours(v))) 1105 { 1106 if (v <= n) 1107 { 1108 assert(g1.edge[e][0] == v); 1109 assert(g1.edge[e][1] == n); 1110 } 1111 else 1112 { 1113 assert(g1.edge[e][0] == n); 1114 assert(g1.edge[e][1] == v); 1115 } 1116 } 1117 } 1118 } 1119 1120 // Check that altering the number of vertices works OK. 1121 g1.vertexCount = 20; 1122 assert(g1.vertexCount == 20); 1123 assertThrown(g1.vertexCount(5)); 1124 g1.vertexCount = 10; 1125 1126 // Check that now we've re-resized it it's back to where it was. 1127 assert(g1.tail_ == g2.tail_); 1128 assert(g1.head_ == g2.head_); 1129 assert(g1.indexTail_ == g2.indexTail_); 1130 assert(g1.indexHead_ == g2.indexHead_); 1131 assert(g1.sumTail_ == g2.sumTail_); 1132 assert(g1.sumHead_ == g2.sumHead_); 1133 } 1134 } 1135 }