www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Enumap -- a lightweight AA alternative when your keys are enums

reply "rcorre" <ryan rcorre.net> writes:
I frequently find myself needing a data structure that maps each 
member of an enum to a value;
something similar what Java calls an EnumMap 
(http://docs.oracle.com/javase/7/docs/api/java/util/EnumMap.html).

I couldn't find any D implementation out there, so I wrote a 
little module for it.
Enumap is available on Github (https://github.com/rcorre/enumap)
and via dub (http://code.dlang.org/packages/enumap).
Docs are hosted at http://rcorre.github.io/enumap/.

An Enumap is basically a thin wrapper that makes a static array 
look like an associative array:

---
enum Attribute {
  strength, dexterity, constitution, wisdom, intellect, charisma
}

Enumap!(Attribute,int) attributes;
attributes[Attribute.strength] = 10;
---

However, you might prefer an Enumap to an associative array if:

You like syntactic sugar:
---
// Boring!
if (hero.attributes[Attribute.wisdom] < 5) 
hero.drink(unidentifiedPotion);

// Fun! And Concise!
if (hero.attributes.wisdom < 5) hero.drink(unidentifiedPotion);
---

You like ranges:
---
// roll for stats!
attributes = generate!(() => uniform!"[]"(1, 20)).take(6);
---

You like default values:
---
int[Attribute] aa;
Enumap!(Attribute, int) em;

aa[Attribute.strength]; // Range violation!
em.strength;            // 0
---

You like array-wise operations:
---
// note the convenient constructor function:
auto bonus = enumap(Attribute.charisma, 2, Attribute.wisdom, 1);

// level up! adds 2 to charisma and 1 to wisdom.
hero.attributes += bonus;
---

You dislike garbage day:
---
       void donFancyHat(int[Attribute] aa) { 
aa[Attribute.charisma] += 1; }
 nogc void donFancyHat(Enumap!(Attribute, int) map) { 
map.charisma += 1; }
---

Check it out, report bugs and all that!

P.S.

The above example used to read:
attributes = sequence!((a,n) => uniform!"[]"(1, 20)).take(6);
before Gary's recently posted article at 
http://nomad.so/2015/08/more-hidden-treasure-in-the-d-standard-library/ made me
realize generate existed.
Sep 10 2015
next sibling parent reply "SimonN" <eiderdaus gmail.com> writes:
On Friday, 11 September 2015 at 02:17:25 UTC, rcorre wrote:
 I frequently find myself needing a data structure that maps 
 each member of an enum to a value;
 something similar what Java calls an EnumMap 
 (http://docs.oracle.com/javase/7/docs/api/java/util/EnumMap.html).

 I couldn't find any D implementation out there, so I wrote a 
 little module for it.
 Enumap is available on Github (https://github.com/rcorre/enumap)
 and via dub (http://code.dlang.org/packages/enumap).
 Docs are hosted at http://rcorre.github.io/enumap/.
Hi, this looks excellent! I've been playing around with it, and am looking forward to using it regularly. I've ran into a compilation error when iterating over a const Enumap. In the following code: import std.stdio; import std.conv; import enumap; enum MyEnum { e1, e2, e3 } struct A { Enumap!(MyEnum, int) map; void mutable_output() { foreach (MyEnum e, int i; map) writefln("%s: %d", e.to!string, i); } void const_output() const { foreach (MyEnum e, const int i; map) writefln("%s: %d", e.to!string, i); } } ...the first method (mutable_output) compiles and works with no errors. The const method, however, gives: source/app.d(19,13): Error: invalid foreach aggregate this.map, define opApply(), range primitives, or use .tupleof". It doesn't seem to matter whether I put const int, or int, in the foreach statement. What's the idiomatic way to loop over a const Enumap? :-) -- Simon
Sep 10 2015
next sibling parent reply "rcorre" <ryan rcorre.net> writes:
On Friday, 11 September 2015 at 03:25:58 UTC, SimonN wrote:
 Hi,

 this looks excellent! I've been playing around with it, and am 
 looking forward to using it regularly.

 I've ran into a compilation error when iterating over a const 
 Enumap. In the following code:

     import std.stdio;
     import std.conv;
     import enumap;

     enum MyEnum { e1, e2, e3 }

     struct A
     {
         Enumap!(MyEnum, int) map;

         void mutable_output()
         {
             foreach (MyEnum e, int i; map)
                 writefln("%s: %d", e.to!string, i);
         }

         void const_output() const
         {
             foreach (MyEnum e, const int i; map)
                 writefln("%s: %d", e.to!string, i);
         }
     }

 ...the first method (mutable_output) compiles and works with no 
 errors. The const method, however, gives:

     source/app.d(19,13): Error: invalid foreach aggregate 
 this.map,
     define opApply(), range primitives, or use .tupleof".

 It doesn't seem to matter whether I put const int, or int, in 
 the foreach statement.

 What's the idiomatic way to loop over a const Enumap? :-)

 -- Simon
Interesting, thanks for pointing that out. I don't think I did a great job with const-correctness here, I'll take a look tomorrow. It should definitely be possible to iterate over (and index, etc...) a const/immutable Enumset, though you're right that it doesn't work right now.
Sep 10 2015
parent reply "SimonN" <eiderdaus gmail.com> writes:
On Friday, 11 September 2015 at 04:02:17 UTC, rcorre wrote:
 On Friday, 11 September 2015 at 03:25:58 UTC, SimonN wrote:
 Hi,
 I've ran into a compilation error when iterating over a const 
 Enumap. In the following code:
Interesting, thanks for pointing that out. I don't think I did a great job with const-correctness here, I'll take a look tomorrow. It should definitely be possible to iterate over (and index, etc...) a const/immutable Enumset, though you're right that it doesn't work right now.
No worries, take your time! Thanks for the quick clarification. I've also tested a couple ways of assigning in a foreach. Continuing from my code above (struct A { Enumap!(MyEnum, int) map; /* ... */ }), I've tried this in the main function: int some_value = 100; A a; foreach (MyEnum e, ref val; a.map) val = ++some_value; a.mutable_output(); foreach (MyEnum e, ref val; a.map) a.map[e] = ++some_value; a.mutable_output(); Output: e1: 0 e2: 0 e3: 0 e1: 104 e2: 105 e3: 106 Since I have been using "ref val" in the first loop, I expected the output to be instead: e1: 101 e2: 102 e3: 103 e1: 104 e2: 105 e3: 106 -- Simon
Sep 10 2015
parent "rcorre" <ryan rcorre.net> writes:
On Friday, 11 September 2015 at 04:30:31 UTC, SimonN wrote:
 Since I have been using "ref val" in the first loop, I expected 
 the output to be instead:

     e1: 101
     e2: 102
     e3: 103
     e1: 104
     e2: 105
     e3: 106

 -- Simon
Yep, as I was looking at the constness thing, I realized that ref parameters in foreach are totally broken. I may just have to use opApply instead of opSlice.
Sep 11 2015
prev sibling parent reply rcorre <ryan rcorre.net> writes:
On Friday, 11 September 2015 at 03:25:58 UTC, SimonN wrote:
 It doesn't seem to matter whether I put const int, or int, in 
 the foreach statement.

 What's the idiomatic way to loop over a const Enumap? :-)

 -- Simon
I've released v0.4.0, which implements foreach with ref, and (hopefully) atones for my crimes against const-correctness. You should be able to modify values in a loop and work with const/immutable Enumaps. Thanks for the feedback!
Sep 12 2015
parent reply SimonN <eiderdaus gmail.com> writes:
On Sunday, 13 September 2015 at 03:33:15 UTC, rcorre wrote:
 I've released v0.4.0, which implements foreach with ref, and 
 (hopefully) atones for my crimes against const-correctness. You 
 should be able to modify values in a loop and work with 
 const/immutable Enumaps.

 Thanks for the feedback!
Yes, the 0.4.x version works with my examples perfectly. Thanks for adding const support! (I haven't tested yet every combination of const/mutable Enumap, const/mutable foraech-value, and direct/ref foreach-value. My examples are exactly what I'd do in normal projects anyway, mapping enum-values to ints.) -- Simon
Sep 15 2015
parent rcorre <ryan rcorre.net> writes:
On Wednesday, 16 September 2015 at 03:20:28 UTC, SimonN wrote:
 Yes, the 0.4.x version works with my examples perfectly. Thanks 
 for adding const support!

 (I haven't tested yet every combination of const/mutable 
 Enumap, const/mutable foraech-value, and direct/ref 
 foreach-value. My examples are exactly what I'd do in normal 
 projects anyway, mapping enum-values to ints.)

 -- Simon
Good to hear! I have a pretty long set of static asserts that tries every (?) operation between mutable/const/immutable enumaps to verify that they do/don't compile as expected, so hopefully that has most situations covered.
Sep 21 2015
prev sibling parent reply "Kagamin" <spam here.lot> writes:
On Friday, 11 September 2015 at 02:17:25 UTC, rcorre wrote:
  nogc void donFancyHat(Enumap!(Attribute, int) map) { 
 map.charisma += 1; }
BTW, what this means? Isn't Enumap a value type?
Sep 11 2015
parent "rcorre" <ryan rcorre.net> writes:
On Friday, 11 September 2015 at 08:22:20 UTC, Kagamin wrote:
 On Friday, 11 September 2015 at 02:17:25 UTC, rcorre wrote:
  nogc void donFancyHat(Enumap!(Attribute, int) map) { 
 map.charisma += 1; }
BTW, what this means? Isn't Enumap a value type?
Correct, the parameter should be passed by ref in that example. Good catch!
Sep 11 2015