1 /*******************************************************************************
2 
3     Map template with a fixed set of keys.
4 
5     Map template with a fixed set of keys, specified in the class' constructor.
6     If an item is added whose key is not in the fixed set, an exception is
7     thrown.
8 
9     Such a map can be faster than a standard hash map, as the fixed set of
10     possible keys means that a fast binary search can be used to find the index
11     of the corresponding value. This of course only holds true when the time
12     taken to generate a hash from a key would be slower than the time taken to
13     do a binary search over the key. In the case of char[] keys, tests have
14     shown that for keys of 5 characters or longer, the FixedKeyMap starts to be
15     faster than the StandardKeyHashingMap, and that in the case of long keys
16     (100 characters) it is an order of magnitude faster.
17 
18     Usage example:
19 
20     ---
21 
22         import ocean.util.container.FixedKeyMap;
23 
24         // Create map instance
25         auto map = new FixedKeyMap!(char[], char[])("first", "second", "third");
26 
27         // Add and check an entry
28         map["first"] = "hello";
29         assert(map["first"] == "hello");
30 
31         // Example of adding an entry which will be rejected
32         try
33         {
34             map["fifth"] = "should fail";
35         }
36         catch ( map.FixedKeyMapException e )
37         {
38             // expected
39         }
40 
41         // Example of checking if a key is in the map (this does not throw an
42         // exception if the key is not found)
43         auto nine = "ninth" in map;
44 
45     ---
46 
47     Copyright:
48         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
49         All rights reserved.
50 
51     License:
52         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
53         Alternatively, this file may be distributed under the terms of the Tango
54         3-Clause BSD License (see LICENSE_BSD.txt for details).
55 
56 *******************************************************************************/
57 
58 module ocean.util.container.FixedKeyMap;
59 
60 
61 
62 
63 import ocean.transition;
64 import ocean.core.Array;
65 import ocean.core.Array: copy, bsearch;
66 import ocean.core.Enforce;
67 
68 debug import ocean.io.Stdout;
69 
70 version(UnitTest) import ocean.core.Test;
71 
72 
73 /*******************************************************************************
74 
75     Fixed key map class template.
76 
77     Params:
78         K = mapping key type
79         V = mapping value type
80 
81 *******************************************************************************/
82 
83 public class FixedKeyMap ( K, V )
84 {
85     /***************************************************************************
86 
87         List of keys in mapping.
88 
89         The keys are set once (in the constructor) and sorted.
90 
91     ***************************************************************************/
92 
93     private K[] keys;
94 
95 
96     /***************************************************************************
97 
98         List of values in mapping. This list is always the same length as the
99         keys array, and has the same ordering (i.e. the value in values[0] is
100         associated with the key in keys[0]).
101 
102     ***************************************************************************/
103 
104     private V[] values;
105 
106 
107     /***************************************************************************
108 
109         Exception instance
110 
111     ***************************************************************************/
112 
113     static public class FixedKeyMapException : Exception
114     {
115         public this ()
116         {
117             super(null);
118             super.file = __FILE__;
119         }
120 
121         public typeof(this) set ( istring msg, long line = __LINE__ )
122         {
123             super.msg = msg;
124             super.line = line;
125             return this;
126         }
127     }
128 
129     private FixedKeyMapException exception;
130 
131 
132     /***************************************************************************
133 
134         Constructor. The passed list of allowed keys is shallow copied into the
135         keys class member.
136 
137         Params:
138             keys = list of allowed keys
139 
140     ***************************************************************************/
141 
142     public this ( Const!(K[]) keys )
143     {
144         this.keys.copy(keys);
145         sort(this.keys);
146 
147         this.values.length = this.keys.length;
148 
149         this.exception = new FixedKeyMapException;
150     }
151 
152 
153     /***************************************************************************
154 
155         Returns:
156             length of mapping (the number of keys)
157 
158     ***************************************************************************/
159 
160     public size_t length ( )
161     {
162         return this.keys.length;
163     }
164 
165 
166     /***************************************************************************
167 
168         Gets a value for a key.
169 
170         Params:
171             key = key to look up
172 
173         Returns:
174             value corresponding to key
175 
176         Throws:
177             if key is not in map (see this.keyIndex)
178 
179     ***************************************************************************/
180 
181     public V opIndex ( Const!(K) key )
182     {
183         return this.values[this.keyIndex(key, true)];
184     }
185 
186 
187     /***************************************************************************
188 
189         Sets a value for a key.
190 
191         Params:
192             value = value to set
193             key = key to set value for
194 
195         Throws:
196             if key is not in map (see this.keyIndex)
197 
198     ***************************************************************************/
199 
200     public void opIndexAssign ( V value, Const!(K) key )
201     {
202         this.values[this.keyIndex(key, true)] = value;
203     }
204 
205 
206     /***************************************************************************
207 
208         Checks whether a key is in the map, and returns a pointer to the
209         corresponding value, or null if the key does not exist.
210 
211         Params:
212             key = key to look up
213 
214         Returns:
215             pointer to value corresponding to key, or null if key not in map
216 
217     ***************************************************************************/
218 
219     public V* opIn_r ( Const!(K) key )
220     {
221         auto pos = this.keyIndex(key, false);
222         auto found = pos < this.keys.length;
223 
224         return found ? &this.values[pos] : null;
225     }
226 
227 
228     /***************************************************************************
229 
230         foreach operator over keys in the map.
231 
232     ***************************************************************************/
233 
234     public int opApply ( scope int delegate ( ref K ) dg )
235     {
236         int res;
237         foreach ( key; this.keys )
238         {
239             res = dg(key);
240             if ( res ) break;
241         }
242         return res;
243     }
244 
245 
246     /***************************************************************************
247 
248         foreach operator over keys and values in the map.
249 
250     ***************************************************************************/
251 
252     public int opApply ( scope int delegate ( ref K, ref V ) dg )
253     {
254         int res;
255         foreach ( i, key; this.keys )
256         {
257             res = dg(key, this.values[i]);
258             if ( res ) break;
259         }
260         return res;
261     }
262 
263 
264     /***************************************************************************
265 
266         foreach operator over keys, values and indices in the map.
267 
268     ***************************************************************************/
269 
270     public int opApply ( scope int delegate ( ref size_t, ref K, ref V ) dg )
271     {
272         int res;
273         foreach ( i, key; this.keys )
274         {
275             res = dg(i, key, this.values[i]);
276             if ( res ) break;
277         }
278         return res;
279     }
280 
281 
282     /***************************************************************************
283 
284         Finds a key in the keys array.
285 
286         Params:
287             key = key to look up
288             throw_if_not_found = if true, an exception is thrown when looking up
289                 a key which isn't in the array
290 
291         Returns:
292             index of key in array, or keys.length if throw_if_not_found is false
293                 and key is not found
294 
295         Throws:
296             if throw_if_not_found is true and the key is not in the array
297 
298     ***************************************************************************/
299 
300     private size_t keyIndex ( Const!(K) key, bool throw_if_not_found )
301     {
302         size_t pos;
303         auto found = ocean.core.Array.bsearch(this.keys, key, pos);
304 
305         if ( !found )
306         {
307             if ( throw_if_not_found )
308             {
309                 throw this.exception.set("Key not in map");
310             }
311             else
312             {
313                 pos = this.keys.length;
314             }
315         }
316 
317         return pos;
318     }
319 }
320 
321 
322 
323 unittest
324 {
325     auto map = new FixedKeyMap!(istring, istring)(["first", "second", "third"]);
326     test(("first" in map) !is null);
327     test(("second" in map) !is null);
328     test(("third" in map) !is null);
329     test(("fourth" in map) is null);
330 
331     test(*("first" in map) == "");
332     test(*("second" in map) == "");
333     test(*("third" in map) == "");
334 
335     map["first"] = "hello";
336     test(("first" in map) !is null);
337     test(*("first" in map) == "hello");
338     test(map["first"] == "hello");
339 
340     map["first"] = "world";
341     test(("first" in map) !is null);
342     test(*("first" in map) == "world");
343     test(map["first"] == "world");
344 
345     bool caught;
346     try
347     {
348         map["fifth"];
349     }
350     catch ( map.FixedKeyMapException e )
351     {
352         caught = true;
353     }
354     test(caught);
355 }