Thread overview
Enumap -- a lightweight AA alternative when your keys are enums
Sep 11, 2015
rcorre
Sep 11, 2015
SimonN
Sep 11, 2015
rcorre
Sep 11, 2015
SimonN
Sep 11, 2015
rcorre
Sep 13, 2015
rcorre
Sep 16, 2015
SimonN
Sep 21, 2015
rcorre
Sep 11, 2015
Kagamin
Sep 11, 2015
rcorre
September 11, 2015
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.
September 11, 2015
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
September 11, 2015
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.
September 11, 2015
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
September 11, 2015
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?
September 11, 2015
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.
September 11, 2015
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!
September 13, 2015
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!

September 16, 2015
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
September 21, 2015
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.