1 module graph; 2 3 import std.algorithm; 4 import std.range; 5 import std.typecons; 6 version (unittest) import dshould; 7 8 alias Dependency = Tuple!(string, "client", string, "supplier"); 9 10 void write(Output)(auto ref Output output, const Dependency[] dependencies) 11 { 12 import std.format : formattedWrite; 13 14 output.put("digraph Dependencies {\n"); 15 output.put("node [shape=box];\n"); 16 foreach (element; dependencies.elements) 17 { 18 output.formattedWrite!(`"%s"`)(element); 19 output.put('\n'); 20 } 21 foreach (dependency; dependencies) 22 { 23 output.formattedWrite!(`"%s" -> "%s"`)(dependency.client, dependency.supplier); 24 output.put('\n'); 25 } 26 output.put("}\n"); 27 } 28 29 @("write dependency graph in the DOT language") 30 unittest 31 { 32 import std.array : appender; 33 import std.string : outdent, stripLeft; 34 35 auto output = appender!string; 36 const dependencies = [Dependency("a", "b")]; 37 38 output.write(dependencies); 39 40 const expected = ` 41 digraph Dependencies { 42 node [shape=box]; 43 "a" 44 "b" 45 "a" -> "b" 46 } 47 `; 48 49 output.data.should.equal(outdent(expected).stripLeft); 50 } 51 52 void transitiveClosure(ref Dependency[] dependencies) 53 { 54 string[] elements = dependencies.elements; 55 56 foreach (element; elements) 57 foreach (client; elements) 58 if (dependencies.canFind(Dependency(client, element))) 59 foreach (supplier; elements) 60 if (dependencies.canFind(Dependency(element, supplier))) 61 dependencies.add(Dependency(client, supplier)); 62 } 63 64 Dependency[] transitiveReduction(ref Dependency[] dependencies) 65 { 66 bool[string] mark = null; 67 Dependency[] cyclicDependencies = null; 68 69 void traverse(string node) 70 { 71 import std.array : array; 72 73 mark[node] = true; 74 foreach (outEdge; dependencies.filter!(a => a.client == node).array) 75 { 76 if (!dependencies.canFind(outEdge)) 77 continue; 78 if (mark.get(outEdge.supplier, false)) 79 { 80 cyclicDependencies.add(outEdge); 81 continue; 82 } 83 foreach (inEdge; dependencies.filter!(a => a.supplier == outEdge.supplier).array) 84 { 85 if (inEdge == outEdge) 86 continue; 87 if (mark.get(inEdge.client, false)) 88 dependencies = dependencies.remove!(a => a == inEdge); 89 } 90 traverse(outEdge.supplier); 91 } 92 mark[node] = false; 93 } 94 95 foreach (element; dependencies.elements) 96 traverse(element); 97 98 return cyclicDependencies; 99 } 100 101 @("apply transitive reduction") 102 unittest 103 { 104 auto dependencies = [Dependency("a", "b"), Dependency("b", "c"), Dependency("a", "c")]; 105 auto cyclicDependencies = transitiveReduction(dependencies); 106 107 dependencies.should.equal([Dependency("a", "b"), Dependency("b", "c")]); 108 cyclicDependencies.should.be.empty; 109 } 110 111 @("apply transitive reduction to cyclic dependencies") 112 unittest 113 { 114 auto dependencies = [Dependency("a", "b"), Dependency("b", "c"), Dependency("c", "a")]; 115 auto cyclicDependencies = transitiveReduction(dependencies); 116 117 dependencies.should.equal([Dependency("a", "b"), Dependency("b", "c"), Dependency("c", "a")]); 118 cyclicDependencies.sort.should.equal(dependencies); 119 } 120 121 string[] elements(in Dependency[] dependencies) 122 { 123 string[] elements = null; 124 125 foreach (dependency; dependencies) 126 { 127 elements.add(dependency.client); 128 elements.add(dependency.supplier); 129 } 130 return elements; 131 } 132 133 void add(Element)(ref Element[] elements, Element element) 134 { 135 if (!elements.canFind(element)) 136 elements ~= element; 137 }