1 /**
2  * An `Enumap` maps each member of an enum to a single value.
3  *
4  * An `Enumap` is effectively a lightweight associative array with some benefits.
5  *
6  * You can think of an `Enumap!(MyEnum, int)` somewhat like a `int[MyEnum]`,
7  * with the following differences:
8  *
9  * - `Enumap!(K,V)` is a value type and requires no dynamic memory allocation
10  *
11  * - `Enumap!(K,V)` has a pre-initialized entry for every member of `K`
12  *
13  * - `Enumap!(K,V)` supports the syntax `map.name` as an alias to `map[K.name]`
14  *
15  * - `Enumap!(K,V)` supports array-wise operations
16  *
17  * Authors: Ryan Roden-Corrent ($(LINK2 https://github.com/rcorre, rcorre))
18  * License: MIT
19  * Copyright: © 2015, Ryan Roden-Corrent
20  *
21  * Examples:
22  * Suppose you are building a good ol' dungeon-crawling RPG.
23  * Where to start? How about the classic 6 attributes:
24  *
25  * ---
26  * enum Attribute {
27  *  strength, dexterity, constitution, wisdom, intellect, charisma
28  * };
29  *
30  * struct Character {
31  *  Enumap!(Attribute, int) attributes;
32  * }
33  * ---
34  *
35  * Lets roll some stats!
36  *
37  * ---
38  * Character hero;
39  * hero.attributes = sequence!((a,n) => uniform!"[]"(0, 20))().take(6);
40  * ---
41  *
42  * Note that we can assign directly from a range!
43  * Just like static array assignment, it will fail if the length doesn't match.
44  *
45  * We can access those values using either `opIndex` or `opDispatch`:
46  *
47  * ---
48  * if (hero.attributes[Attribute.wisdom] < 5) hero.drink(unidentifiedPotion);
49  * // equivalent
50  * if (hero.attributes.wisdom < 5) hero.drink(unidentifiedPotion);
51  * ---
52  *
53  * We can also perform binary operations between `Enumap`s:
54  *
55  * ---
56  * // note the convenient assignment from an associative array:
57  * Enumap!(Attribute, int) bonus = {Attribute.charisma: 2, Attribute.wisom: 1};
58  *
59  * // level up! adds 2 to charisma and 1 to wisdom.
60  * hero.attributes += bonus;
61  * ---
62  *
63  * Finally, note that we can break the `Enumap` down into a range when needed:
64  *
65  * ---
66  * hero.attributes = hero.attributes.byValue.map!(x => x + 1);
67  * ---
68  *
69  * See the full documentation of `Enumap` for all the operations it supports.
70  *
71  */
72 module enumap;
73 
74 import std.conv      : to;
75 import std.range;
76 import std.traits    : Unqual, EnumMembers;
77 import std.typecons  : tuple, staticIota;
78 import std.algorithm : map;
79 
80 /**
81  * A structure that maps each member of an enum to a single value.
82  *
83  * An `Enumap` is a lightweight alternative to an associative array that is
84  * useable when your key type is an enum.
85  *
86  * It provides some added benefits over the AA, such as array-wise operations,
87  * default values for all keys, and some nice `opDispatch` based syntactic
88  * sugar for element access.
89  *
90  * The key enum must be backed by an integral type and have 'default' numbering.
91  * The backing value must start at 0 and grows by 1 for each member.
92  *
93  * Params:
94  * K = The type of enum used as a key.
95  *     The enum values must start at 0, and increase by 1 for each entry.
96  * V = The type of value stored for each enum member
97  */
98 struct Enumap(K, V)
99   if(EnumMembers!K == staticIota!(0, EnumMembers!K.length))
100 {
101   /// The number of entries in the `Enumap`
102   enum length = EnumMembers!K.length;
103 
104   /// Assuming Element consists of air, earth, water, and fire (4 members):
105   unittest {
106     static assert(Enumap!(Element, int).length == 4);
107   }
108 
109   private V[length] _store;
110 
111   /// Construct an Enumap from a static array.
112   this(V[length] values) {
113     _store = values;
114   }
115 
116   ///
117   @nogc unittest {
118     auto elements = Enumap!(Element, int)([1, 2, 3, 4]);
119 
120     assert(elements[Element.air]   == 1);
121     assert(elements[Element.earth] == 2);
122     assert(elements[Element.water] == 3);
123     assert(elements[Element.fire]  == 4);
124   }
125 
126   /// Assign from a range with a number of elements exactly matching `length`.
127   this(R)(R values) if (isInputRange!R && is(ElementType!R : V)) {
128     int i = 0;
129     foreach(val ; values) {
130       assert(i < length, "range contains more values than Enumap");
131       _store[i++] = val;
132     }
133     assert(i == length, "range contains less values than Enumap");
134   }
135 
136   ///
137   @nogc unittest {
138     import std.range : repeat;
139     Enumap!(Element, int) elements = 9.repeat(4);
140     assert(elements.air   == 9);
141     assert(elements.earth == 9);
142     assert(elements.water == 9);
143     assert(elements.fire  == 9);
144   }
145 
146   /// An Enumap can be assigned from an array or range of values
147   void opAssign(T)(T val) if (is(typeof(typeof(this)(val)))) {
148     this = typeof(this)(val);
149   }
150 
151   /// Assign an Enumap from a static array.
152   @nogc unittest {
153     Enumap!(Element, int) elements;
154     int[elements.length] arr = [1, 2, 3, 4];
155     elements = arr;
156 
157     with (Element) {
158       assert(elements[air]   == 1);
159       assert(elements[earth] == 2);
160       assert(elements[water] == 3);
161       assert(elements[fire]  == 4);
162     }
163   }
164 
165   /// Assign an Enumap from a range
166   @nogc unittest {
167     import std.range : iota;
168 
169     Enumap!(Element, int) elements;
170 
171     with (Element) {
172       elements = iota(0, 4);
173 
174       assert(elements[air]   == 0);
175       assert(elements[earth] == 1);
176       assert(elements[water] == 2);
177       assert(elements[fire]  == 3);
178     }
179   }
180 
181   /**
182    * Access the value at the index specified by an enum member.
183    *
184    * Indexing returns a reference, so it can be used as a getter or setter.
185    */
186   ref auto opIndex(K key) inout {
187     return _store[key];
188   }
189 
190   ///
191   @nogc unittest {
192     Enumap!(Element, int) elements;
193     elements[Element.fire] = 4;
194     assert(elements[Element.fire] == 4);
195   }
196 
197   /**
198    * Access the value at the index specified by the name of an enum member.
199    *
200    * The value is returned by reference, so it can used for assignment.
201    * `map.name` is just syntactic sugar for `map[SomeEnum.name]`.
202    */
203   ref auto opDispatch(string s)() inout {
204     enum key = s.to!K;
205     return this[key];
206   }
207 
208   ///
209   @nogc unittest {
210     Enumap!(Element, int) elements;
211     elements.water = 5;
212     assert(elements.water == 5);
213   }
214 
215   /// Execute a foreach statement over (EnumMember, value) pairs.
216   int opApply(scope int delegate(K, const V) dg) const {
217     // declare the callee as @nogc so @nogc callers can use foreach
218     // oddly, this works even if dg is non-nogc... huh?
219     // I'm just gonna take this and run before the compiler catches me.
220     auto callme = cast(int delegate(K, const V) @nogc) dg;
221 
222     int res = 0;
223 
224     foreach(key ; EnumMembers!K) {
225       res = callme(key, this[key]);
226       if (res) break;
227     }
228 
229     return res;
230   }
231 
232   /// foreach iterates over (EnumMember, value) pairs.
233   @nogc unittest {
234     const auto elements = enumap(Element.water, 4, Element.air, 3);
235 
236     foreach(key, value ; elements) {
237       assert(
238           key == Element.water && value == 4 ||
239           key == Element.air   && value == 3 ||
240           value == 0);
241     }
242   }
243 
244   /// Execute foreach over (EnumMember, ref value) pairs to modify elements.
245   int opApply(scope int delegate(K, ref V) dg) {
246     // declare the callee as @nogc so @nogc callers can use foreach
247     // oddly, this works even if dg is non-nogc... huh?
248     // I'm just gonna take this and run before the compiler catches me.
249     auto callme = cast(int delegate(K, ref V) @nogc) dg;
250 
251     int res = 0;
252 
253     foreach(key ; EnumMembers!K) {
254       res = callme(key, this[key]);
255       if (res) break;
256     }
257 
258     return res;
259   }
260 
261   /// foreach can modify values by ref
262   @nogc unittest {
263     Enumap!(Element, int) elements;
264 
265     foreach(key, ref value ; elements) {
266       if      (key == Element.air)   value = 4;
267       else if (key == Element.water) value = 2;
268     }
269 
270     assert(elements.air   == 4);
271     assert(elements.water == 2);
272   }
273 
274   // make sure you can foreach with a non-nogc delegate
275   unittest {
276     foreach(key, ref value ; enumap(Element.water, "here comes..."))
277       value ~= "a spurious allocation!";
278   }
279 
280   /// Apply an array-wise operation between two `Enumap`s.
281   auto opBinary(string op)(inout typeof(this) other) const
282     if (is(typeof(mixin("V.init"~op~"V.init")) : V))
283   {
284     Unqual!(typeof(this)) result;
285 
286     foreach(member ; EnumMembers!K) {
287       result[member] = mixin("this[member]"~op~"other[member]");
288     }
289 
290     return result;
291   }
292 
293   ///
294   @nogc unittest {
295     immutable base  = enumap(Element.water, 4, Element.air , 3);
296     immutable bonus = enumap(Element.water, 5, Element.fire, 2);
297 
298     immutable sum  = base + bonus;
299     immutable diff = base - bonus;
300     immutable prod = base * bonus;
301 
302     assert(sum.water == 4 + 5);
303     assert(sum.air   == 3 + 0);
304     assert(sum.fire  == 0 + 2);
305     assert(sum.earth == 0 + 0);
306 
307     assert(diff.water == 4 - 5);
308     assert(diff.air   == 3 - 0);
309     assert(diff.fire  == 0 - 2);
310     assert(diff.earth == 0 - 0);
311 
312     assert(prod.water == 4 * 5);
313     assert(prod.air   == 3 * 0);
314     assert(prod.fire  == 0 * 2);
315     assert(prod.earth == 0 * 0);
316   }
317 
318   ///
319   unittest {
320     auto inventory = enumap(
321         ItemType.junk  , [ "Gemstone"        ],
322         ItemType.normal, [ "Sword", "Shield" ],
323         ItemType.key   , [ "Bronze Key"      ]);
324 
325     auto loot = enumap(
326         ItemType.junk  , [ "Potato"       ],
327         ItemType.normal, [ "Potion"       ],
328         ItemType.key   , [ "Skeleton Key" ]);
329 
330     inventory ~= loot;
331 
332     assert(inventory.junk   == [ "Gemstone", "Potato"          ]);
333     assert(inventory.normal == [ "Sword", "Shield" , "Potion"  ]);
334     assert(inventory.key    == [ "Bronze Key" , "Skeleton Key" ]);
335   }
336 
337   /// Perform an in-place operation.
338   auto opOpAssign(string op)(inout typeof(this) other)
339     if (is(typeof(this.opBinary!op(other)) : typeof(this)))
340   {
341     this = this.opBinary!op(other);
342   }
343 
344   ///
345   @nogc unittest {
346     auto  base  = enumap(Element.water, 4, Element.air , 3);
347     const bonus = enumap(Element.water, 5, Element.fire, 2);
348 
349     base += bonus;
350 
351     assert(base.water == 4 + 5);
352     assert(base.air   == 3 + 0);
353     assert(base.fire  == 0 + 2);
354     assert(base.earth == 0 + 0);
355 
356     base -= bonus; // cancel out the previous addition
357 
358     assert(base.water == 4);
359     assert(base.air   == 3);
360     assert(base.fire  == 0);
361     assert(base.earth == 0);
362 
363     base *= bonus;
364     assert(base.water == 4 * 5);
365     assert(base.air   == 3 * 0);
366     assert(base.fire  == 0 * 2);
367     assert(base.earth == 0 * 0);
368   }
369 
370   /// Perform a unary operation on each entry.
371   auto opUnary(string op)() const
372     if (is(typeof(mixin(op~"V.init")) : V))
373   {
374     V[length] result = mixin(op~"_store[]");
375     return typeof(this)(result);
376   }
377 
378   @nogc unittest {
379     immutable elements = enumap(Element.water, 4);
380     assert((-elements).water == -4);
381   }
382 
383   /// Get a range iterating over the members of the enum `K`.
384   auto byKey() const { return only(EnumMembers!K); }
385 
386   @nogc unittest {
387     import std.range     : only;
388     import std.algorithm : equal;
389 
390     Enumap!(Element, int) e;
391     with (Element) {
392       assert(e.byKey.equal(only(air, earth, water, fire)));
393     }
394   }
395 
396   /// Get a range iterating over the stored values.
397   auto byValue() inout { return _store[]; }
398 
399   /// you can use byValue to perform range based operations on the values:
400   @nogc unittest {
401     import std.range     : iota;
402     import std.algorithm : map, equal;
403 
404     const Enumap!(Element, int) e1 = iota(0, 4);
405     const Enumap!(Element, int) e2 = e1.byValue.map!(x => x + 2);
406     assert(e2.byValue.equal(iota(2, 6)));
407   }
408 
409   /// `byValue` supports ref access:
410   @nogc unittest {
411     with (Element) {
412       auto elements = enumap(air, 1, water, 2, fire, 3, earth, 4);
413       foreach(ref val ; elements.byValue) ++val;
414       assert(elements == enumap(air, 2, water, 3, fire, 4, earth, 5));
415     }
416   }
417 
418   /**
419    * Return a range of (EnumMember, value) pairs.
420    *
421    * Note that byKeyValue does _not_ support modifying the underlying values by
422    * reference.
423    * For that, you should just use foreach directly (see `opApply`).
424    */
425   auto byKeyValue() const {
426     return only(EnumMembers!K).map!(key => tuple(key, this[key]));
427   }
428 
429   ///
430   @nogc unittest {
431     import std.typecons  : tuple;
432     import std.algorithm : map;
433 
434     immutable elements = enumap(Element.water, 4, Element.air, 3);
435 
436     auto pairs = elements.byKeyValue.map!(pair => tuple(pair[0], pair[1] + 1));
437 
438     foreach(key, value ; pairs) {
439       assert(
440           key == Element.water && value == 5 ||
441           key == Element.air   && value == 4 ||
442           value == 1);
443     }
444   }
445 }
446 
447   /**
448    * Construct an `Enumap` from a sequence of key/value pairs.
449    *
450    * Any values not specified default to `V.init`.
451    */
452 auto enumap(T...)(T pairs) @nogc if (T.length >= 2 && T.length % 2 == 0) {
453   alias K = T[0];
454   alias V = T[1];
455 
456   Enumap!(K, V) result;
457 
458   // pop a key/vaue pair, assign it to the enumap, and recurse until empty
459   void helper(U...)(U params) {
460     static assert(is(U[0] == K), "enumap: mismatched key type");
461     static assert(is(U[1] == V), "enumap: mismatched value type");
462 
463     auto key = params[0];
464     auto val = params[1];
465 
466     result[key] = val;
467 
468     static if (U.length > 2) {
469       helper(params[2..$]);
470     }
471   }
472 
473   helper(pairs);
474   return result;
475 }
476 
477 ///
478 @nogc unittest {
479   with (Element) {
480     auto elements = enumap(air, 1, earth, 2, water, 3);
481 
482     assert(elements[air]   == 1);
483     assert(elements[earth] == 2);
484     assert(elements[water] == 3);
485     assert(elements[fire]  == 0); // unspecified values default to V.init
486   }
487 }
488 
489 version (unittest) {
490   // define some enums for testing
491   private enum Element { air, earth, water, fire };
492   private enum ItemType { junk, normal, key };
493 }
494 
495 // make sure the readme examples work:
496 unittest {
497   import std.algorithm, std.range, std.random;
498 
499   enum Attribute {
500     strength, dexterity, constitution, wisdom, intellect, charisma
501   }
502 
503   Enumap!(Attribute, int) attributes;
504   assert(attributes.wisdom == 0); // default value check
505 
506   attributes[Attribute.strength] = 10;
507 
508   attributes = generate!(() => uniform!"[]"(1, 20)).take(6);
509 
510   // make sure accessors compile:
511   if (attributes[Attribute.wisdom] < 5) { }
512   if (attributes.wisdom < 5) { }
513 
514   // key/value constructor
515   auto bonus = enumap(Attribute.charisma, 2, Attribute.wisdom, 1);
516 
517   // opBinary
518   attributes += bonus;
519 
520   // nogc test
521   void donFancyHat(int[Attribute] attrs) { attrs[Attribute.charisma] += 1; }
522   @nogc void donFancyHat2(Enumap!(Attribute, int) attrs) { attrs.charisma += 1; }
523 }
524 
525 // constness tests:
526 unittest {
527   auto      mmap = enumap(Element.air, 2, Element.water, 4);
528   const     cmap = enumap(Element.air, 2, Element.water, 4);
529   immutable imap = enumap(Element.air, 2, Element.water, 4);
530 
531   // value access permitted on all
532   static assert(__traits(compiles, mmap[Element.air] == 2));
533   static assert(__traits(compiles, cmap[Element.air] == 2));
534   static assert(__traits(compiles, imap[Element.air] == 2));
535   static assert(__traits(compiles, mmap.air == 2));
536   static assert(__traits(compiles, cmap.air == 2));
537   static assert(__traits(compiles, imap.air == 2));
538 
539   // value setters only permitted if mutable
540   static assert( __traits(compiles, { mmap[Element.air] = 2; }));
541   static assert(!__traits(compiles, { cmap[Element.air] = 2; }));
542   static assert(!__traits(compiles, { imap[Element.air] = 2; }));
543   static assert( __traits(compiles, { mmap.air = 2; }));
544   static assert(!__traits(compiles, { cmap.air = 2; }));
545   static assert(!__traits(compiles, { imap.air = 2; }));
546 
547   // readonly foreach permitted on all
548   static assert(__traits(compiles, { foreach(k,v ; mmap) {} }));
549   static assert(__traits(compiles, { foreach(k,v ; cmap) {} }));
550   static assert(__traits(compiles, { foreach(k,v ; imap) {} }));
551 
552   // foreach with modification permitted only if mutable
553   static assert( __traits(compiles, { foreach(k, ref v ; mmap) {++v;} }));
554   static assert(!__traits(compiles, { foreach(k, ref v ; cmap) {++v;} }));
555   static assert(!__traits(compiles, { foreach(k, ref v ; imap) {++v;} }));
556 
557   // opBinary permitted on all
558   static assert(__traits(compiles, { immutable res = mmap + mmap; }));
559   static assert(__traits(compiles, { immutable res = cmap + cmap; }));
560   static assert(__traits(compiles, { immutable res = imap + imap; }));
561   static assert(__traits(compiles, { immutable res = mmap + imap; }));
562   static assert(__traits(compiles, { immutable res = imap + mmap; }));
563 
564   // opBinaryAssign permitted only if mutable
565   static assert( __traits(compiles, { mmap += mmap; }));
566   static assert( __traits(compiles, { mmap += cmap; }));
567   static assert( __traits(compiles, { mmap += imap; }));
568   static assert(!__traits(compiles, { cmap += mmap; }));
569   static assert(!__traits(compiles, { cmap += cmap; }));
570   static assert(!__traits(compiles, { imap += imap; }));
571 
572   // opUnary permitted on all
573   static assert(__traits(compiles, { -mmap; }));
574   static assert(__traits(compiles, { -cmap; }));
575   static assert(__traits(compiles, { -imap; }));
576 
577   // opUnary permitted on all
578   static assert(__traits(compiles, { -mmap; }));
579   static assert(__traits(compiles, { -cmap; }));
580   static assert(__traits(compiles, { -imap; }));
581 
582   // byKey permitted on all
583   static assert(__traits(compiles, { mmap.byKey; }));
584   static assert(__traits(compiles, { cmap.byKey; }));
585   static assert(__traits(compiles, { imap.byKey; }));
586 
587   // byValue permitted on all
588   static assert(__traits(compiles, { foreach(v ; mmap.byValue) {} }));
589   static assert(__traits(compiles, { foreach(v ; cmap.byValue) {} }));
590   static assert(__traits(compiles, { foreach(v ; imap.byValue) {} }));
591 
592   // byValue with ref assignment permitted only if mutable
593   static assert( __traits(compiles, { foreach(ref v ; mmap.byValue) ++v; }));
594   static assert(!__traits(compiles, { foreach(ref v ; cmap.byValue) ++v; }));
595   static assert(!__traits(compiles, { foreach(ref v ; imap.byValue) ++v; }));
596 
597   // byKeyValue permitted on all
598   static assert(__traits(compiles, { foreach(k, v ; mmap.byKeyValue) {} }));
599   static assert(__traits(compiles, { foreach(k, v ; cmap.byKeyValue) {} }));
600   static assert(__traits(compiles, { foreach(k, v ; imap.byKeyValue) {} }));
601 }