1 module anansi.adjacencylist; 2 3 import std.algorithm, 4 std.conv, 5 std.range, 6 std.stdio, 7 std.traits, 8 std.typecons, 9 std.typetuple; 10 import anansi.container, anansi.traits; 11 12 13 struct AdjacencyList (alias VertexStorage = VecS, 14 alias EdgeStorage = VecS, 15 Directionality = DirectedS, 16 VertexProperty = NoProperty, 17 EdgeProperty = NoProperty) 18 { 19 public: 20 alias VertexDescriptor = VertexStorage.IndexType; 21 22 enum IsUndirected = is(Directionality == UndirectedS); 23 enum IsBidirectional = is(Directionality == BidirectionalS); 24 enum IsDirected = !(IsUndirected || IsBidirectional); 25 26 /** 27 * The main storage object for edges. Holds the source and destination 28 * vertices for the graph, plus any property object. 29 */ 30 private static struct Edge { 31 this(VertexDescriptor src, VertexDescriptor dst, 32 EdgeProperty value = EdgeProperty.init) 33 { 34 _src = src; 35 _dst = dst; 36 static if (isNotNone!EdgeProperty) { 37 _property = value; 38 } 39 } 40 41 private VertexDescriptor _src; 42 private VertexDescriptor _dst; 43 44 public @property VertexDescriptor source() { return _src; } 45 public @property void source(VertexDescriptor v) { _src = v; } 46 47 public @property VertexDescriptor target() { return _dst; } 48 public @property void target(VertexDescriptor v) { _dst = v; } 49 50 private EdgeProperty _property; 51 public @property ref inout(EdgeProperty) value() inout { 52 return _property; 53 } 54 } 55 56 private alias EdgeList = List!Edge; 57 private alias EdgeIndex = EdgeList.Node*; 58 59 /** 60 * A handle that can be used by callers to identify a given edge. 61 */ 62 public static struct EdgeDescriptor { 63 package VertexDescriptor src; 64 package VertexDescriptor dst; 65 package const(EdgeList.Node)* edgeIndex; 66 } 67 68 /** 69 * The main storage object for vertices. Maintains the collection of out- 70 * (and optionally in-) edges for the vertex, plus any property values. 71 */ 72 private static struct Vertex { 73 public alias EdgeContainer = EdgeStorage.Store!EdgeIndex; 74 75 private EdgeContainer _outEdges; 76 static if (IsBidirectional) { 77 private EdgeContainer _inEdges; 78 } 79 80 public this(VertexProperty p) { 81 static if (isNotNone!VertexProperty) { 82 _property = p; 83 } 84 } 85 86 public this(this) { 87 _outEdges = _outEdges.dup; 88 static if (IsBidirectional) { 89 _inEdges = _inEdges.dup; 90 } 91 } 92 93 public auto addOutEdge(EdgeIndex edge) { 94 return _outEdges.push(edge); 95 } 96 97 public void eraseOutEdge(EdgeIndex edge) { 98 eraseEdge(_outEdges, edge); 99 } 100 101 private static void eraseEdge(ref EdgeContainer edges, EdgeIndex edge) { 102 auto r = find(edges[], edge); 103 assert (!r.empty, "Attempt to remove an edge that doesn't exist"); 104 edges.eraseFrontOfRange(r); 105 } 106 107 auto outEdges() const { return _outEdges[]; } 108 109 @property public size_t outDegree() const { 110 return _outEdges.length; 111 } 112 113 static if (IsBidirectional) { 114 public void addInEdge(EdgeIndex edge) { 115 _inEdges.push(edge); 116 } 117 118 public void eraseInEdge(EdgeIndex edge) { 119 eraseEdge(_inEdges, edge); 120 } 121 122 auto inEdges() const { return _inEdges[]; } 123 124 @property public size_t inDegree() const { 125 return _inEdges.length; 126 } 127 } 128 129 static if (isNotNone!VertexProperty) { 130 private VertexProperty _property; 131 132 public @property ref inout(VertexProperty) property() inout { 133 return _property; 134 } 135 } 136 } 137 138 /** 139 * The main vertex store. 140 */ 141 VertexStorage.Store!Vertex _vertices; 142 143 /** 144 * The main edge store 145 */ 146 EdgeList _edges; 147 148 public: 149 // ------------------------------------------------------------------------ 150 // Vertex operations 151 // ------------------------------------------------------------------------ 152 153 /** 154 * Adds a new vertex to the graph. 155 * 156 * Params: 157 * value = The property value to associate with the vertex, if any. 158 * 159 * Returns: Returns a VertexDescriptor referencing the newly-added vertex. 160 */ 161 VertexDescriptor addVertex(VertexProperty value = VertexProperty.init) { 162 return _vertices.push(Vertex(value)).index; 163 } 164 165 @property vertexCount() const { 166 return _vertices.length; 167 } 168 169 /** 170 * Returns a forward range that yields the descriptors for each vertex in 171 * the graph. The order of the vertices is undefined. 172 */ 173 @property auto vertices() inout { 174 return _vertices.indexRange(); 175 } 176 177 static if (!isNone!VertexProperty) { 178 ref inout(VertexProperty) opIndex(VertexDescriptor v) inout { 179 return _vertices.get_value(v).property; 180 } 181 } 182 183 /** 184 * Removes a vertex from the graph. The complexity of this operaton 185 * varies with the storage class. For storage classes that have 186 * stable indexes, this is O(1). For classes with unstable indexes 187 * this is at least O(n), where n is the number of edges in the graph, 188 * due to the VertexDescriptors in all the edges having to be fixed up. 189 * 190 * This is the *minimum* complexity, because the underlying storage may 191 * impose its own complexity costs on erasing the vertex itself as well. 192 * 193 * Params: 194 * vertex = The VertexDescriptor of the vertex to erase. 195 */ 196 void removeVertex(VertexDescriptor vertex) { 197 _vertices.erase(vertex); 198 static if( !VertexStorage.IndexesAreStable ) { 199 foreach(ref e; _edges[]) { 200 e.source = _vertices.rewriteIndex(vertex, e.source); 201 e.target = _vertices.rewriteIndex(vertex, e.target); 202 } 203 } 204 } 205 206 public: 207 // ------------------------------------------------------------------------ 208 // Edge operations 209 // ------------------------------------------------------------------------ 210 211 /** 212 * A tuple with some names to help unpacking the result of an addEdge call. 213 */ 214 alias AddEdgeResult = Tuple!(EdgeDescriptor, "edge", bool, "addedNew"); 215 216 /** 217 * Adds an edge to the graph. 218 * 219 * Params: 220 * src = The VertexDescriptor of the edge's starting point. 221 * dst = The VertexDescriptor of the new edge's end point. 222 * value = The value to associate with the new edge, if any. 223 * 224 * Returns: Returns an AddEdgeResult value, containinf the descriptor of 225 * the edge, and a flag to let you know if it was newly created 226 * (true), or the edge already esisted and the graph type doesn't 227 * support parallel edges, so the returned descriptor refers to 228 * the pre-existing edge (false). 229 */ 230 AddEdgeResult addEdge(VertexDescriptor src, 231 VertexDescriptor dst, 232 EdgeProperty value = EdgeProperty.init) { 233 234 EdgeIndex newEdge = _edges.insertBack(Edge(src, dst, value)); 235 Vertex* pSrc = &_vertices.get_value(src); 236 pSrc.addOutEdge(newEdge); 237 238 static if (IsUndirected) { 239 Vertex* pDst = &_vertices.get_value(dst); 240 pDst.addOutEdge(newEdge); 241 } 242 else static if (IsBidirectional) { 243 Vertex* pDst = &_vertices.get_value(dst); 244 pDst.addInEdge(newEdge); 245 } 246 247 return AddEdgeResult(EdgeDescriptor(src, dst, newEdge), true); 248 } 249 250 /** 251 * Removes an edge from the graph. 252 */ 253 void removeEdge(EdgeDescriptor edge) { 254 VertexDescriptor src = cast(VertexDescriptor) edge.src; 255 VertexDescriptor dst = cast(VertexDescriptor) edge.dst; 256 Vertex* srcVertex = &_vertices.get_value(src); 257 EdgeIndex idx = cast(EdgeIndex) edge.edgeIndex; 258 259 srcVertex.eraseOutEdge(idx); 260 261 static if (IsUndirected) { 262 Vertex* dstVertex = &_vertices.get_value(dst); 263 dstVertex.eraseOutEdge(idx); 264 } 265 else static if (IsBidirectional) { 266 Vertex* dstVertex = &_vertices.get_value(dst); 267 dstVertex.eraseInEdge(idx); 268 } 269 270 _edges.remove(idx); 271 } 272 273 /** 274 * Fetches the descriptor of the source vertex of the given edge. 275 * 276 * Params: 277 * edge = The descriptor of the edge to query. 278 * 279 * Returns: The descriptor of the supplied edge's source vertex. 280 */ 281 VertexDescriptor source(EdgeDescriptor edge) const { 282 return cast(VertexDescriptor) edge.src; 283 } 284 285 /** 286 * Fetches the descriptor of the target vertex of the supplied edge. 287 * 288 * Params: 289 * edge = The descriptor of the edge you want to query. 290 */ 291 VertexDescriptor target(EdgeDescriptor edge) const { 292 return cast(VertexDescriptor) edge.dst; 293 } 294 295 /** 296 * Lists the outbound edges of a given vertex. 297 * 298 * Params: 299 * vertex = The descriptor of the vertex to query. 300 * 301 * Returns: Returns a range containing the edge descriptors of the 302 * supplied vertex's outbound edges. 303 */ 304 auto outEdges(VertexDescriptor vertex) const { 305 static struct OutEdgeRange { 306 alias EdgeRange = Vertex.EdgeContainer.ConstRange; 307 308 this(VertexDescriptor src, EdgeRange r) { 309 _src = src; 310 _r = r; 311 } 312 313 @property bool empty() { return _r.empty; } 314 315 @property EdgeDescriptor front() { 316 auto edge = _r.front; 317 318 static if (IsUndirected) { 319 auto s = edge.value._src; 320 auto d = edge.value._dst; 321 auto src = (s == _src) ? s : d; 322 auto dst = (s == _src) ? d : s; 323 } 324 else{ 325 auto src = edge.value._src; 326 auto dst = edge.value._dst; 327 } 328 329 return EdgeDescriptor(cast(VertexDescriptor)src, 330 cast(VertexDescriptor)dst, 331 cast(EdgeIndex)(edge)); 332 } 333 334 void popFront() { _r.popFront(); } 335 336 private EdgeRange _r; 337 private VertexDescriptor _src; 338 } 339 340 static assert(isInputRange!OutEdgeRange); 341 static assert(is(ElementType!OutEdgeRange == EdgeDescriptor)); 342 343 return OutEdgeRange(vertex, _vertices.get_value(vertex).outEdges()); 344 } 345 346 size_t outDegree(VertexDescriptor vertex) const { 347 return _vertices.get_value(vertex).outDegree; 348 } 349 350 static if (IsBidirectional) { 351 size_t inDegree(VertexDescriptor vertex) const { 352 return _vertices.get_value(vertex).inDegree; 353 } 354 355 auto inEdges(VertexDescriptor vertex) const { 356 static struct InEdgeRange { 357 alias EdgeRange = Vertex.EdgeContainer.ConstRange; 358 359 this(EdgeRange r) { _r = r; } 360 361 @property bool empty() { return _r.empty; } 362 363 @property EdgeDescriptor front() { 364 auto edge = _r.front; 365 auto s = edge.value._src; 366 auto d = edge.value._dst; 367 return EdgeDescriptor(cast(VertexDescriptor)s, 368 cast(VertexDescriptor)d, 369 cast(EdgeIndex)edge); 370 } 371 372 void popFront() { _r.popFront(); } 373 374 private EdgeRange _r; 375 private VertexDescriptor _src; 376 } 377 378 static assert(isInputRange!InEdgeRange); 379 static assert(is(ElementType!InEdgeRange == EdgeDescriptor)); 380 381 return InEdgeRange(_vertices.get_value(vertex).inEdges()); 382 } 383 } 384 385 static if (!isNone!EdgeProperty) { 386 ref EdgeProperty opIndex(EdgeDescriptor e) { 387 return (cast(EdgeIndex)e.edgeIndex).valueRef.value; 388 } 389 390 ref const(EdgeProperty) opIndex(EdgeDescriptor e) const { 391 return e.edgeIndex.valueRef.value; 392 } 393 } 394 395 /** 396 * Fetches the total number of edges in the graph. For debugging purposes 397 * only. 398 */ 399 @property size_t edgeCount() const { 400 return _edges.length; 401 } 402 403 auto edgesBetween(VertexDescriptor from, VertexDescriptor to) { 404 return filter!(e => target(e) == to)(outEdges(from)); 405 } 406 } 407 408 // ---------------------------------------------------------------------------- 409 // Unit Tests 410 // ---------------------------------------------------------------------------- 411 412 unittest { 413 writeln("AdjacencyList: Custom storage type"); 414 415 struct ArrayS { 416 alias IndexType = size_t; 417 enum IndexesAreStable = true; 418 419 static struct Store(ValueType) { 420 alias Range = ValueType[]; 421 422 auto push(ValueType value) { 423 size_t rval = _store.length; 424 _store ~= value; 425 return PushResult!(typeof(rval))(rval, true); 426 } 427 428 void erase(IndexType index) { 429 assert (false, "Not Implemented"); 430 } 431 432 void eraseFrontOfRange(ValueType[] range) { 433 assert (false, "Not Implemented"); 434 } 435 436 auto indexRange() const { 437 return iota(0, _store.length); 438 } 439 440 ref inout(ValueType) get_value(IndexType index) inout { 441 return _store[index]; 442 } 443 444 @property auto dup() { 445 return Store(_store.dup); 446 } 447 448 @property size_t length() const { 449 return _store.length; 450 } 451 452 ValueType[] _store; 453 454 alias ConstRange = const(ValueType)[]; 455 456 alias _store this; 457 } 458 } 459 460 AdjacencyList!(ArrayS, ArrayS, DirectedS, char, string) g; 461 auto v = g.addVertex('a'); 462 assert (g[v] == 'a'); 463 } 464 465 unittest { 466 writeln("AdjacencyList: Adding a vertex with no property."); 467 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 468 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 469 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 470 AdjacencyList!(VertexStorage, EdgeStorage, Directionality) g; 471 auto v1 = g.addVertex(); 472 auto v2 = g.addVertex(); 473 auto v3 = g.addVertex(); 474 475 assert (g.vertexCount == 3, 476 "Bad vertex count. Exepected 3, got " ~ 477 to!string(g.vertexCount)); 478 479 foreach(x; zip(g.vertices(), [v1, v2, v3][])) { 480 assert (x[0] == x[1], "Mismatched vertex descriptors"); 481 } 482 } 483 } 484 } 485 } 486 487 unittest { 488 writeln("AdjacencyList: Adding a vertex with a property."); 489 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 490 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 491 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 492 AdjacencyList!(VertexStorage, EdgeStorage, Directionality, string) g; 493 auto v1 = g.addVertex("alpha"); 494 auto v2 = g.addVertex("beta"); 495 auto v3 = g.addVertex("gamma"); 496 497 foreach(x; zip(g.vertices(), ["alpha", "beta", "gamma"][])) { 498 assert (g[x[0]] == x[1], "Mismatched vertex descriptors"); 499 } 500 } 501 } 502 } 503 } 504 505 unittest { 506 writeln("AdjacencyList: Vertex properties are assignable."); 507 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 508 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 509 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 510 AdjacencyList!(VertexStorage, EdgeStorage, Directionality, string) g; 511 auto v1 = g.addVertex("alpha"); 512 auto v2 = g.addVertex("beta"); 513 auto v3 = g.addVertex("gamma"); 514 515 assert (g[v2] == "beta"); 516 g[v2] = "narf"; 517 518 foreach(x; zip(g.vertices(), ["alpha", "narf", "gamma"][])) { 519 assert (g[x[0]] == x[1], "Mismatched vertex properties"); 520 } 521 } 522 } 523 } 524 } 525 526 unittest { 527 struct Test { int i; } 528 529 writeln("AdjacencyList: Vertex properties are mutable."); 530 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 531 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 532 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 533 AdjacencyList!(VertexStorage, EdgeStorage, Directionality, Test) g; 534 auto v1 = g.addVertex(Test(0)); 535 auto v2 = g.addVertex(Test(1)); 536 auto v3 = g.addVertex(Test(2)); 537 538 assert (g[v2].i == 1); 539 g[v2].i = 42; 540 assert (g[v2].i == 42); 541 } 542 } 543 } 544 } 545 546 unittest { 547 writeln("AdjacencyList: Erasing a vertex."); 548 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 549 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 550 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 551 alias Graph = AdjacencyList!(VertexStorage, EdgeStorage, Directionality, char, string); 552 Graph g; 553 554 /* artificial scope for namespacing */ { 555 auto a = g.addVertex('a'); 556 auto b = g.addVertex('b'); 557 auto c = g.addVertex('c'); 558 559 auto ac = g.addEdge(a, c, "ac"); 560 auto ca = g.addEdge(c, a, "ca"); 561 562 g.removeVertex(b); 563 } 564 565 assert (g.vertexCount == 2, 566 Graph.stringof ~ ": Expected vertex count to be 2, got: " ~ 567 to!string(g.vertexCount)); 568 569 // assert that we only have the vertices we expect 570 auto vertices = array(g.vertices()); 571 foreach(x; zip(vertices, ['a', 'c'])) { 572 assert (g[x[0]] == x[1], 573 Graph.stringof ~ ": Mismatched vertex descriptors"); 574 } 575 576 // assert the a -> c edge still holds 577 auto a = vertices[0]; 578 auto c = vertices[1]; 579 auto ac = g.outEdges(a).front; 580 assert (g.source(ac) == a, 581 Graph.stringof ~ ": Source(ac) should be a"); 582 583 assert (g.target(ac) == c, 584 Graph.stringof ~ ": Target(ac) should be c"); 585 586 // assert the c -> a edge still holds 587 auto ca = g.outEdges(c).front; 588 assert (g.source(ca) == c, 589 Graph.stringof ~ ": Source(ca) should be c"); 590 591 assert (g.target(ca) == a, 592 Graph.stringof ~ ": Target(ca) should be a"); 593 594 } 595 } 596 } 597 } 598 599 version (unittest) { 600 void checkEdges(Graph)(ref Graph g, 601 string name, 602 Graph.VertexDescriptor src, 603 Graph.VertexDescriptor[] outTargets, 604 Graph.VertexDescriptor[] inTargets) { 605 auto outEdges = array(g.outEdges(src)); 606 auto outDegree = g.outDegree(src); 607 assert (outDegree == outTargets.length, 608 Graph.stringof ~ ": Expected " ~ name ~ 609 " to have out degree of " ~ to!string(outTargets.length) ~ 610 ", got " ~ to!string(outDegree)); 611 612 assert (outEdges.length == outTargets.length, 613 Graph.stringof ~ ": Expected " ~ name ~ " to have exactly " ~ 614 to!string(outTargets.length) ~ " out edge(s), got " ~ 615 to!string(outEdges.length)); 616 617 foreach (t; outTargets) { 618 assert (any!(e => g.target(e) == t)(outEdges), 619 Graph.stringof ~ ": Expected target from " ~ name ~ 620 " was not in out edges."); 621 } 622 623 static if (g.IsBidirectional) { 624 auto inDegree = g.inDegree(src); 625 assert (inDegree == inTargets.length, 626 Graph.stringof ~ ": Expected " ~ name ~ 627 " to have in degree of " ~ to!string(inTargets.length) ~ 628 ", got " ~ to!string(inDegree)); 629 630 auto inEdges = array(g.inEdges(src)); 631 foreach (t; inTargets) { 632 assert (any!(e => g.source(e) == t)(inEdges), 633 Graph.stringof ~ ": Expected source in " ~ name ~ 634 " was not in in-edges."); 635 } 636 } 637 } 638 } 639 640 unittest { 641 writeln("AdjacencyList: Adding edges works as expected."); 642 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 643 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 644 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 645 alias Graph = AdjacencyList!(VertexStorage, EdgeStorage, Directionality, char, string); 646 647 Graph g; 648 649 // Graph layout 650 // B 651 // / | \ 652 // A | C 653 // | / 654 // D 655 656 auto vA = g.addVertex('a'); 657 auto vB = g.addVertex('b'); 658 auto vC = g.addVertex('c'); 659 auto vD = g.addVertex('d'); 660 661 auto addUniqueEdge = delegate(Graph.VertexDescriptor s, Graph.VertexDescriptor d) { 662 auto tmp = g.addEdge(s, d, to!string(g[s]) ~ " --> " ~ to!string(g[d]) ); 663 assert(tmp.addedNew, Graph.stringof ~ ": Edge must be unique."); 664 return tmp.edge; 665 }; 666 667 auto eAB = addUniqueEdge(vA, vB); 668 auto eBC = addUniqueEdge(vB, vC); 669 auto eCD = addUniqueEdge(vC, vD); 670 auto eBD = addUniqueEdge(vB, vD); 671 672 assert (g.edgeCount == 4, "edgeCount should be 4"); 673 674 checkEdges!Graph(g, "A", vA, [vB], []); 675 checkEdges!Graph(g, "B", vB, g.IsUndirected ? [vA, vC, vD] : [vC, vD], [vA]); 676 checkEdges!Graph(g, "C", vC, g.IsUndirected ? [vB, vD] : [vD], [vB]); 677 checkEdges!Graph(g, "D", vD, g.IsUndirected ? [vB, vC] : [], [vB, vC]); 678 } 679 } 680 } 681 } 682 683 unittest { 684 import std.algorithm.comparison : equal; 685 686 writeln("AdjacencyList: Removing edges works as expected."); 687 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 688 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 689 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 690 alias Graph = AdjacencyList!(VertexStorage, EdgeStorage, Directionality, char, string); 691 692 Graph g; 693 694 // Graph layout 695 // B 696 // / | 697 // A | C 698 // | / 699 // D 700 701 auto vA = g.addVertex('a'); 702 auto vB = g.addVertex('b'); 703 auto vC = g.addVertex('c'); 704 auto vD = g.addVertex('d'); 705 706 auto addUniqueEdge = delegate(Graph.VertexDescriptor s, Graph.VertexDescriptor d) { 707 auto tmp = g.addEdge(s, d, to!string(g[s]) ~ " --> " ~ to!string(g[d]) ); 708 assert(tmp.addedNew, Graph.stringof ~ ": Edge must be unique."); 709 return tmp.edge; 710 }; 711 712 auto eAB = addUniqueEdge(vA, vB); 713 auto eBC = addUniqueEdge(vB, vC); 714 auto eCD = addUniqueEdge(vC, vD); 715 auto eBD = addUniqueEdge(vB, vD); 716 717 assert (equal(g.edgesBetween(vA, vB), [eAB])); // TODO: Check for multigraph. 718 719 assert (g.edgeCount == 4, "edgeCount should be 4"); 720 721 g.removeEdge(eBC); 722 723 assert (g.edgeCount == 3, "edgeCount should be 3"); 724 725 checkEdges!Graph(g, "A", vA, [vB], []); 726 checkEdges!Graph(g, "B", vB, g.IsUndirected ? [vA, vD] : [vD], [vA]); 727 checkEdges!Graph(g, "C", vC, g.IsUndirected ? [vD] : [vD], []); 728 checkEdges!Graph(g, "D", vD, g.IsUndirected ? [vB, vC] : [], [vB, vC]); 729 730 assert (g[eAB] == "a --> b"); 731 assert (g[eBD] == "b --> d"); 732 assert (g[eCD] == "c --> d"); 733 } 734 } 735 } 736 } 737 738 unittest { 739 writeln("AdjacencyList: Edge properties are mutable."); 740 741 foreach(VertexStorage; TypeTuple!(VecS, ListS)) { 742 foreach(EdgeStorage; TypeTuple!(VecS, ListS)) { 743 foreach(Directionality; TypeTuple!(DirectedS, UndirectedS, BidirectionalS)) { 744 alias Graph = AdjacencyList!(VertexStorage, EdgeStorage, Directionality, char, string); 745 746 Graph g; 747 748 // Graph layout 749 // B 750 // / | 751 // A | C 752 // | / 753 // D 754 755 auto vA = g.addVertex('a'); 756 auto vB = g.addVertex('b'); 757 auto vC = g.addVertex('c'); 758 auto vD = g.addVertex('d'); 759 760 auto addUniqueEdge = delegate(Graph.VertexDescriptor s, Graph.VertexDescriptor d) { 761 auto tmp = g.addEdge(s, d, to!string(g[s]) ~ " --> " ~ to!string(g[d]) ); 762 assert(tmp.addedNew, Graph.stringof ~ ": Edge must be unique."); 763 return tmp.edge; 764 }; 765 766 auto eAB = addUniqueEdge(vA, vB); 767 auto eBC = addUniqueEdge(vB, vC); 768 auto eCD = addUniqueEdge(vC, vD); 769 auto eBD = addUniqueEdge(vB, vD); 770 771 assert (g[eBC] == "b --> c", "Property should be readable"); 772 g[eBC] = "some value"; 773 assert (g[eBC] == "some value"); 774 } 775 } 776 } 777 }