1 /*
2     This file is part of BioD.
3     Copyright (C) 2012    Artem Tarasov <lomereiter@gmail.com>
4 
5     Permission is hereby granted, free of charge, to any person obtaining a
6     copy of this software and associated documentation files (the "Software"),
7     to deal in the Software without restriction, including without limitation
8     the rights to use, copy, modify, merge, publish, distribute, sublicense,
9     and/or sell copies of the Software, and to permit persons to whom the
10     Software is furnished to do so, subject to the following conditions:
11     
12     The above copyright notice and this permission notice shall be included in
13     all copies or substantial portions of the Software.
14     
15     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21     DEALINGS IN THE SOFTWARE.
22 
23 */
24 module bio.core.tinymap;
25 
26 private import std.algorithm;
27 private import std.range;
28 private import std.traits;
29 
30 import std.bitmanip;
31 
32 /// Efficient dictionary for cases when the number of possible keys is small 
33 /// and is known at compile-time. The data is held in a static array, no allocations occur.
34 ///
35 /// Key type must:
36 ///     have static member ValueSetSize (integer)
37 ///     have static method fromInternalCode (returning an instance of Key type);
38 ///     have property $(D internal_code) that maps it to integers 0, 1, ..., ValueSetSize - 1
39 struct TinyMap(K, V, alias TinyMapPolicy=useBitArray) {
40     private V[K.ValueSetSize] _dict;
41     private size_t _size;
42     private mixin TinyMapPolicy!(K, V) Policy;
43     private alias ReturnType!(K.internal_code) TCode;
44 
45     /// Constructor
46     static TinyMap!(K, V, TinyMapPolicy) opCall(Args...)(Args args) {
47         TinyMap!(K, V, TinyMapPolicy) result;
48         result.Policy.init(args);
49         return result;
50     }
51    
52     /// Current number of elements
53     size_t length() @property const {
54         return _size;
55     }
56 
57     /// Indexed access
58     auto ref V opIndex(Key)(auto ref Key key)
59         if(is(Unqual!Key == K))
60     {
61         assert(key in this);
62         return _dict[key.internal_code];
63     }
64 
65     /// ditto
66     auto ref const(V) opIndex(Key)(auto ref Key key) const
67         if(is(Unqual!Key == K))
68     {
69         assert(key in this);
70         return _dict[key.internal_code];
71     }
72 
73 
74     /// ditto
75     V opIndexAssign(V value, K key) {
76         if (key !in this) {
77             ++_size;
78         }
79         _dict[key.internal_code] = value;
80         Policy._onInsert(key);
81         return value;
82     }
83 
84     /// ditto
85     void opIndexOpAssign(string op)(V value, K key) {
86         if (key !in this) {
87             ++_size;
88             _dict[key.internal_code] = V.init;
89         }
90         mixin("_dict[key.internal_code] " ~ op ~ "= value;");
91         Policy._onInsert(key);
92     }
93 
94     /// Check if the key is in the dictionary
95     bool opIn_r(K key) const {
96         return Policy._hasKey(key);
97     }
98 
99     /// Removal
100     bool remove(K key) {
101         if (key in this) {
102             --_size;
103             Policy._onRemove(key);
104             return true;
105         }
106         return false;
107     }
108 
109     /// Range of keys
110     auto keys() @property const {
111         // FIXME: create nice workaround for LDC bug #217
112         K[] _ks;
113         foreach (i; 0 .. K.ValueSetSize) {
114             if (Policy._hasKeyWithCode(i))
115                 _ks ~= K.fromInternalCode(cast(TCode)i);
116         }
117         return _ks;
118     }
119 
120     /// Range of values
121     auto values() @property const {
122         V[] _vs;
123         foreach (i; 0 .. K.ValueSetSize) {
124             if (Policy._hasKeyWithCode(i))
125                 _vs ~= _dict[i];
126         }
127         return _vs;
128     }
129 
130     /// Iteration with foreach
131     int opApply(scope int delegate(V value) dg) {
132         foreach (i; iota(K.ValueSetSize)) {
133             if (Policy._hasKeyWithCode(i)) {
134                 auto ret = dg(_dict[i]);
135                 if (ret != 0) return ret;
136             }
137         }
138         return 0;
139     }
140 
141     /// ditto
142     int opApply(scope int delegate(K key, V value) dg) {
143         foreach (i; iota(K.ValueSetSize)) {
144             if (Policy._hasKeyWithCode(i)) {
145                 auto ret = dg(K.fromInternalCode(cast(TCode)i), _dict[i]);
146                 if (ret != 0) return ret;
147             }
148         }
149         return 0;
150     }
151 }
152 
153 /// For each possible key store 0 if it's absent in the dictionary,
154 /// or 1 otherwise. Bit array is used for compactness.
155 ///
156 /// This is the default option. In this case, size of dictionary is
157 /// roughly (V.sizeof + 1/8) * K.ValueSetSize
158 mixin template useBitArray(K, V) {
159     private BitArray _value_is_set;
160 
161     private void init() {
162         _value_is_set.length = K.ValueSetSize;
163     }
164 
165     private bool _hasKey(K key) const {
166         return _value_is_set[key.internal_code];
167     }
168 
169     private bool _hasKeyWithCode(size_t code) const {
170         return _value_is_set[code];
171     }
172 
173     private void _onInsert(K key) {
174         _value_is_set[key.internal_code] = true;
175     }
176 
177     private void _onRemove(K key) {
178         _value_is_set[key.internal_code] = false;
179     }
180 }
181 
182 /// Use default value specified at construction as an indicator
183 /// of key absence.
184 /// That allows to save K.ValueSetSize bits of memory.
185 ///
186 /// E.g., you might want to use -1 as such indicator if non-negative
187 /// numbers are stored in the dictionary.
188 mixin template useDefaultValue(K, V) {
189     private V _default_value;
190 
191     private void init(V value) {
192         _default_value = value;
193         if (_default_value != V.init) {
194             _dict[] = _default_value;
195         }
196     }
197 
198     private bool _hasKey(K key) const {
199         return _dict[key.internal_code] != _default_value;
200     }
201 
202     private bool _hasKeyWithCode(size_t code) const {
203         return _dict[code] != _default_value;
204     }
205 
206     private void _onInsert(K key) {}
207 
208     private void _onRemove(K key) {
209         this[key] = _default_value;
210     }
211 }
212 
213 /// Allows to set up a dictionary which is always full.
214 mixin template fillNoRemove(K, V) {
215 
216     private void init() {
217         _size = K.ValueSetSize;
218     }
219 
220     private void init(V value) {
221         _size = K.ValueSetSize;
222 
223         for (size_t i = 0; i < _size; ++i)
224             _dict[i] = value;
225     }
226 
227     private bool _hasKey(K key) const {
228         return true;
229     }
230 
231     private bool _hasKeyWithCode(size_t code) const {
232         return true;
233     }
234 
235     private void _onInsert(K key) {}
236 
237     private void _onRemove(K key) {
238         ++_size;
239     }
240 }
241 
242 unittest {
243 
244     import std.array;
245     import bio.core.base;
246 
247     void test(M)(ref M dict) {
248         auto b1 = Base('A');
249         auto b2 = Base('C');
250         auto b3 = Base('G');
251         auto b4 = Base('T');
252         dict[b1] = 2;
253         dict[b2] = 3;
254         assert(dict.length == 2);
255         assert(dict[b1] == 2);
256         assert(b2 in dict);
257         assert(b3 !in dict);
258         assert(b4 !in dict);
259         dict[b4] = 5;
260         assert(equal(sort(array(dict.values)), [2, 3, 5]));
261         dict.remove(b1);
262         assert(b1 !in dict);
263         assert(dict.length == 2);
264         assert(dict[b2] == 3);
265 
266         foreach (k, v; dict) {
267             assert(k in dict);
268             assert(dict[k] == v);
269         }
270     }
271 
272     auto dict1 = TinyMap!(Base, int)();
273     auto dict2 = TinyMap!(Base, int, useDefaultValue)(-1);
274     int[Base] dict3;
275 
276     test(dict1);
277     test(dict2);
278     test(dict3);
279 
280     auto dict4 = TinyMap!(Base, ulong[4])();
281     dict4[Base('A')] = [0, 1, 2, 3];
282     dict4[Base('A')][3] += 1;
283     assert(dict4[Base('A')] == [0, 1, 2, 4]);
284 }
285 
286 /// Convenient mixin template for getting your struct working with TinyMap.
287 ///
288 /// Creates
289 ///     1) private member of type T with name _code
290 ///     2) fromInternalCode static method
291 ///     3) internal_code property
292 ///     4) static member ValueSetSize equal to N
293 ///     5) invariant that _code is always less than ValueSetSize
294 ///
295 /// That is, the only thing which implementation is up to you is
296 /// setting _code appropriately.
297 mixin template TinyMapInterface(uint N, T=ubyte) if (isUnsigned!T) {
298     private T _code;
299 
300     enum ValueSetSize = N;
301     static assert(N <= 2 ^^ (T.sizeof * 8));
302 
303     static typeof(this) fromInternalCode(T code) {
304         typeof(this) obj = void;
305         obj._code = code;
306         return obj;
307     }
308 
309     T internal_code() @property const {
310         return _code;
311     }
312 }