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