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 }