The OpenD Programming Language

1 /++
2 Basic routines to work with graphs.
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
6 Authors: Ilia Ki
7 
8 Macros:
9 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, graph, $1)$(NBSP)
10 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
11 +/
12 
13 module mir.graph;
14 
15 import mir.math.common: fmamath;
16 
17 import mir.series;
18 import mir.rc.array;
19 import mir.ndslice.iterator: ChopIterator;
20 
21 ///
22 alias GraphIterator(I = uint, J = size_t) = ChopIterator!(size_t*, uint*);
23 ///
24 alias Graph(I = uint, J = size_t) = Slice!(GraphIterator!(I, J));
25 ///
26 alias GraphSeries(T, I = uint, J = size_t) = Series!(T*, GraphIterator!(I, J));
27 
28 ///
29 alias RCGraphIterator(I = uint, J = size_t) = ChopIterator!(RCI!size_t, RCI!uint);
30 ///
31 alias RCGraph(I = uint, J = size_t) = Slice!(RCGraphIterator!(I, J));
32 ///
33 alias RCGraphSeries(T, I = uint, J = size_t) = Series!(RCI!T, RCGraphIterator!(I, J));
34 
35 private static immutable exc_msg = "graphSeries: graph should contains keys for all vertixes";
36 version(D_Exceptions)
37 {
38     private static immutable exception = new Exception(exc_msg);
39 }
40 
41 /++
42 Params:
43     aaGraph = graph that is represented as associative array
44 Returns:
45     A graph series composed of keys (sorted `.index`) and arrays of indeces (`.data`)
46 Complexity: `O(log(V) (V + E))`
47 +/
48 @fmamath
49 GraphSeries!(T, I, J) graphSeries(I = uint, J = size_t, T, Range)(in Range[T] aaGraph)
50 {
51     import mir.array.allocation: array;
52     import mir.ndslice.sorting;
53     import mir.ndslice;
54     auto keys = aaGraph.byKey.array.sliced;
55     sort(keys);
56     size_t dataLength;
57     foreach (ref v; aaGraph)
58         dataLength += v.length;
59     auto data = uninitSlice!I(dataLength);
60     auto components = uninitSlice!J(keys.length + 1);
61     size_t dataIndex;
62 
63     foreach (i; 0 .. keys.length)
64     {
65         components[i] = cast(J) dataIndex;
66         foreach(ref elem; aaGraph[keys[i]])
67         {
68             import mir.ndslice.sorting: transitionIndex;
69             auto index = keys.transitionIndex(elem);
70             if (index >= keys.length)
71             {
72                 version(D_Exceptions)
73                     { import mir.exception : toMutable; throw exception.toMutable; }
74                 else
75                     assert(0, exc_msg);
76             }
77             data[dataIndex++] = cast(I) index;
78         }
79     }
80     components[keys.length] = dataIndex; 
81     auto sliceable = (() @trusted => data.ptr)();
82     return keys.series(sliceable.chopped(components));
83 }
84 
85 ///
86 pure version(mir_test) unittest
87 {
88     auto gs = [
89         "b" : ["a"],
90         "a" : ["b", "c"],
91         "c" : ["b"],
92     ].graphSeries;
93 
94     assert (gs.index == ["a", "b", "c"]); // sorted
95     assert (gs.data == [
96         [1, 2], // a
97         [0],    // b
98         [1],    // c
99     ]);
100 }
101 
102 /++
103 Params:
104     graph = graph that is represented a series
105 Returns:
106     A graph as an arrays of indeces
107 Complexity: `O(log(V) (V + E))`
108 +/
109 @fmamath
110 RCGraph!(I, J) rcgraph(I = uint, J = size_t, KeyIterator, RangeIterator)(Series!(KeyIterator, RangeIterator) graph)
111 {
112     import mir.array.allocation: array;
113     import mir.ndslice.sorting;
114     import mir.ndslice;
115     auto scopeGraph = graph.lightScope;
116     auto keys = scopeGraph.index;
117     auto graphData = scopeGraph.data;
118     size_t dataLength;
119     foreach (ref v; graphData)
120         dataLength += v.length;
121     auto data = rcslice!I(dataLength);
122     auto components = rcslice!J(keys.length + 1);
123     size_t dataIndex;
124 
125     foreach (i; 0 .. keys.length)
126     {
127         components[i] = cast(J) dataIndex;
128         foreach(ref elem; graphData[i])
129         {
130             import mir.ndslice.sorting: transitionIndex;
131             auto index = keys.transitionIndex(elem);
132             if (index >= keys.length)
133             {
134                 version(D_Exceptions)
135                     { import mir.exception : toMutable; throw exception.toMutable; }
136                 else
137                     assert(0, exc_msg);
138             }
139             data[dataIndex++] = cast(I) index;
140         }
141     }
142     components[keys.length] = dataIndex; 
143     return data._iterator.chopped(components);
144 }
145 
146 ///
147 @safe pure @nogc version(mir_test)
148 unittest
149 {
150     import mir.series: series;
151 
152     static immutable keys = ["a", "b", "c"];
153     static immutable data = [
154         ["b", "c"],
155         ["a"],
156         ["b"],
157     ];
158 
159     static immutable graphValue = [
160         [1, 2], // a
161         [0],    // b
162         [1],    // c
163     ];
164 
165     assert (series(keys, data).rcgraph == graphValue);
166 }