1 module dtrie;
2 
3 import map : WordNodeNumberMap;
4 import lib.exception : ValueError, KeyError;
5 
6 
7 struct IterableKeys {
8     /*
9        T is the type of a value.
10      */
11 
12     private WordNodeNumberMap key_to_node_number;
13     //node numbers each of which contains at least one value
14     uint[] node_has_value;
15 
16     this(T)(WordNodeNumberMap key_to_node_number,
17             T[][] node_number_value_map) {
18         this.key_to_node_number = key_to_node_number;
19 
20         foreach(uint node_number, T[] values; node_number_value_map) {
21             if(values.length > 0) {
22                 this.node_has_value ~= node_number;
23             }
24         }
25     }
26 
27     int opApply(int delegate(string) dg) {
28         int result;
29 
30         //if node_number_value_map[node_number] has value, traverse a tree from
31         //a leaf to get the corresponding word
32         foreach(uint node_number; this.node_has_value) {
33             string word = this.key_to_node_number.getWord(node_number);
34 
35             result = dg(word);
36             if(result != 0) {
37                 break;
38             }
39         }
40         return result;
41     }
42 }
43 
44 
45 interface Map(T) {
46     //this(string[] keys, T[] values);
47     T[] get(string key);
48     IterableKeys byKey();
49 }
50 
51 
52 class WordWordMap : Map!string {
53     /*
54     This is a string-to-string dictionary implemented using
55     a copule of trie trees.
56     Trie provides a function which returns the node number given the key, and
57     also has a function which returns the key given the node number.
58 
59     Algorithm:
60         Construction:
61             1. Build trie trees. One is with keys, and another one is with
62             values.
63             2. Associate node numbers given by each trie tree by array.
64         Indexing access:
65             1. Extract the node number associated with the key.
66             2. Using a node number given by the key, obtain the corresponding
67             node number of the trie tree build with values.
68             3. Extract the value by the node number obtained at 2.
69      */
70 
71     private WordNodeNumberMap key_to_node_number;
72     private WordNodeNumberMap node_number_to_value;
73 
74     //Array to associate node numbers given from each key and node numbers
75     //given from each value.
76     //2D array is used to associate multiple values to one key.
77     private uint[][] node_number_map;
78 
79     this(string[] keys, string[] values)
80     body {
81         this.key_to_node_number = new WordNodeNumberMap(keys);
82         this.node_number_to_value = new WordNodeNumberMap(values);
83 
84         foreach(uint i, key; keys) {
85             this.associateNodeNumbers(key, values[i]);
86         }
87     }
88 
89     /**
90       Returns a forward range which will iterate over the keys of the dictionary.
91      */
92     @property IterableKeys byKey() {
93         return IterableKeys(this.key_to_node_number,
94                             this.node_number_map);
95     }
96 
97     private void associateNodeNumbers(string key, string value) {
98         uint k = this.key_to_node_number.getNodeNumber(key);
99         uint v = this.node_number_to_value.getNodeNumber(value);
100 
101         //expand the associative array
102         if(k >= this.node_number_map.length) {
103             auto size = k+1-this.node_number_map.length;
104             this.node_number_map ~= new uint[][](size, 0); //additional space
105         }
106         this.node_number_map[k] ~= v;
107     }
108 
109     /**
110       Return the values given the key.
111       Empty array will be returned if the key doesn't exist in the
112       key set.
113      */
114     string[] get(string key) {
115         string[] values;
116         uint k = this.key_to_node_number.getNodeNumber(key);
117         uint[] node_numbers = this.node_number_map[k];
118 
119         if(node_numbers.length == 0) {
120             throw new KeyError(key);
121         }
122 
123         foreach(v; node_numbers) {
124             values ~= this.node_number_to_value.getWord(v);
125         }
126 
127         return values;
128     }
129 }
130 
131 
132 ///string to string
133 unittest {
134     auto dictionary = new WordWordMap(["Win", "hot"], ["Lose", "cold"]);
135 
136     assert(dictionary.get("Win") == ["Lose"]);
137     assert(dictionary.get("hot") == ["cold"]);
138 
139     bool error_thrown = false;
140     try {
141         dictionary.get("won");
142     } catch(KeyError e) {
143         error_thrown = true;
144     }
145     assert(error_thrown);
146 }
147 
148 
149 //smoke test
150 unittest {
151     string[] keys = [
152         "accept", "affirm", "include", "arrive", "invest",
153         "begin", "offer", "conceal", "discharge", "recognize",
154         "enrich", "rise", "expose", "remember", "sleep",
155         "hide", "sink", "hurt", "wax", "accept",
156         "affirm", "include", "arrive", "invest", "begin",
157         "offer", "conceal", "discharge", "recognize", "enrich",
158         "rise", "expose", "remember", "sleep", "hide",
159         "sink", "hurt", "wax"
160     ];
161 
162     string[] values = [
163         "reject", "deny", "exclude", "depart", "divest",
164         "end", "refuse", "reveal", "convict", "ignore",
165         "impoverish", "fall, set", "conceal", "forget",
166         "wake", "seek", "swim", "heal", "wane",
167         "reject", "deny", "exclude", "depart", "divest",
168         "end", "refuse", "reveal", "convict", "ignore",
169         "impoverish", "fall, set", "conceal", "forget",
170         "wake", "seek", "swim", "heal", "wane"
171     ];
172 
173     auto dictionary = new WordWordMap(keys, values);
174     foreach(ulong i, string key; keys) {
175         assert(dictionary.get(key)[0] == values[i]);
176     }
177 }
178 
179 
180 class WordObjectMap(T) : Map!T {
181     /*
182     string-to-object dictionary
183     Algorithm:
184         Construction:
185             1. Build a trie tree with keys.
186             2. Associate each node number to each corresponding value.
187         Indexing access:
188             1. Extract the node number associated with the key.
189             2. Obtain the corresponding value by the node number.
190      */
191 
192     private WordNodeNumberMap key_to_node_number;
193 
194     //Array to associate node numbers given from each key and node numbers
195     //given from each value.
196     //2D array is used to associate multiple values to one key.
197     private T[][] node_number_value_map;
198 
199     this(string[] keys, T[] values)
200     body {
201         this.key_to_node_number = new WordNodeNumberMap(keys);
202 
203         foreach(ulong i, key; keys) {
204             this.associateNodeNumberToValue(key, values[i]);
205         }
206     }
207 
208     @property IterableKeys byKey() {
209         return IterableKeys(this.key_to_node_number,
210                             this.node_number_value_map);
211     }
212 
213     private void associateNodeNumberToValue(string key, T value) {
214         uint k = this.key_to_node_number.getNodeNumber(key);
215 
216         //expand the associative array
217         if(k >= this.node_number_value_map.length) {
218             auto size = k+1-this.node_number_value_map.length;
219             this.node_number_value_map ~= new T[][](size, 0); //additional space
220         }
221         this.node_number_value_map[k] ~= value;
222     }
223 
224     /**
225       Return the values given the key.
226       Raise KeyError if key not found.
227      */
228     T[] get(string key) {
229         uint k = this.key_to_node_number.getNodeNumber(key);
230         return this.node_number_value_map[k];
231     }
232 }
233 
234 
235 ///string to int
236 unittest {
237     auto dictionary = new WordObjectMap!(int)(["one", "two"], [1, 2]);
238     assert(dictionary.get("one") == [1]);
239     assert(dictionary.get("two") == [2]);
240 
241 
242     bool error_thrown = false;
243     try {
244         dictionary.get("three");
245     } catch(KeyError e) {
246         error_thrown = true;
247     }
248     assert(error_thrown);
249 }
250 
251 
252 //TODO try to make only this class public
253 class DTrie(T) {
254     private Map!(T) map;
255 
256     this(string[] keys, T[] values)
257     in {
258         if(keys.length != values.length) {
259             throw new ValueError(
260                 "The number of keys doesn't match the number of values.");
261         }
262     }
263     body {
264         static if(is(T : string)) {
265             //string specific map (less memory)
266             this.map = new WordWordMap(keys, values);
267         } else {
268             //string-to-anytype map
269             this.map = new WordObjectMap!(T)(keys, values);
270         }
271     }
272 
273     @property IterableKeys byKey() {
274         return this.map.byKey;
275     }
276     unittest {
277         import std.container;
278         import std.range;
279         import std.array;
280         import std.algorithm;
281 
282         alias Set = make!(RedBlackTree!string);
283 
284         void testByKey(T)(string[] keys, T[] values) {
285             auto map = new DTrie!T(keys, values);
286 
287             auto words = Set();
288             foreach(word; map.byKey) {
289                 words.insert(word);
290             }
291 
292             assert(equal(words[], keys));
293         }
294 
295         //test for string-to-anytype map
296         testByKey(["a", "an", "i", "of", "on", "one", "our", "out"],
297                   [0, 1, 2, 3, 4, 5, 6, 7]);
298         //test for string-to-string map
299         testByKey(["a", "an", "i", "of", "on", "one", "our", "out"],
300                   ["A", "AN", "I", "OF", "ON", "ONE", "OUR", "OUT"]);
301     }
302 
303     T[] opIndex(string key) {
304         return this.map.get(key);
305     }
306 }
307 
308 
309 ///
310 unittest {
311     auto dictionary = new DTrie!string(["Win", "hot"], ["Lose", "cold"]);
312     assert(dictionary["Win"] == ["Lose"]);
313     assert(dictionary["hot"] == ["cold"]);
314 
315 
316     bool error_thrown = false;
317     try {
318         dictionary["won"];
319     } catch(KeyError e) {
320         error_thrown = true;
321     }
322     assert(error_thrown);
323 }
324 
325 
326 ///
327 unittest {
328     auto dictionary = new DTrie!int(["one", "two"], [1, 2]);
329     assert(dictionary["one"] == [1]);
330     assert(dictionary["two"] == [2]);
331 
332     bool error_thrown = false;
333     try {
334         dictionary["three"];
335     } catch(KeyError e) {
336         error_thrown = true;
337     }
338     assert(error_thrown);
339 }
340 
341 
342 /// Multiple strings are available
343 //test for multibyte strings
344 unittest {
345     string[] keys = [
346         "すもーくちーず"
347     ];
348 
349     string[] values = [
350         "スモークチーズ"
351     ];
352 
353     auto dictionary = new DTrie!(string)(keys, values);
354     assert(dictionary["すもーくちーず"] == ["スモークチーズ"]);
355 }
356 
357 
358 unittest {
359     auto america = new DTrie!string(
360             ["Capital", "Currency"],
361             ["Washington, D.C.", "Dollar"]);
362 
363     auto china = new DTrie!string(
364             ["Capital", "Currency"],
365             ["Beijing", "Renminbi"]);
366 
367     auto japan = new DTrie!string(
368             ["Capital", "Currency"],
369             ["Tokyo", "Yen"]);
370 
371     auto countries = new DTrie!(DTrie!string)(
372             ["America", "China", "Japan"],
373             [america, china, japan]);
374 
375     assert(countries["America"][0]["Capital"] == ["Washington, D.C."]);
376     assert(countries["America"][0]["Currency"] == ["Dollar"]);
377 
378     assert(countries["China"][0]["Capital"] == ["Beijing"]);
379     assert(countries["China"][0]["Currency"] == ["Renminbi"]);
380 
381     assert(countries["Japan"][0]["Capital"] == ["Tokyo"]);
382     assert(countries["Japan"][0]["Currency"] == ["Yen"]);
383 }
384 
385 
386 //test for duplicate values
387 unittest {
388     string[] keys = [
389         "あけます", "あけます", "あけます",
390         "あけました", "あけました", "あけました"
391     ];
392 
393     string[] values = [
394         "開けます", "明けます", "空けます",
395         "開けました", "明けました", "空けました"
396     ];
397 
398     auto dictionary = new DTrie!(string)(keys, values);
399     assert(dictionary["あけます"] == ["開けます", "明けます", "空けます"]);
400     assert(dictionary["あけました"] ==
401             ["開けました", "明けました", "空けました"]);
402 }