www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Efficient enum array keys?

reply Julian <julian.fondren gmail.com> writes:
Hello,

When reading through the following D blog post, I noticed in the
feature chart that D had "Arrays beginning at arbitrary indices" 
as
a +1 feature, the same as in Ada.

https://dlang.org/blog/2018/06/20/how-an-engineering-company-chose-to-migrate-to-d/

That surprised me, and from the code with the blog, that seems to 
be
generous. In Ada you can just say

   Silly : array (2 .. 7) of Integer;

to stack-allocate a six-integer array, with the first accessible
integer at Silly(2) and the last integer at Silly(7). Meanwhile 
the
blog has some epcomat thing that provides this to D:

   alias t = StaticArray!(int, 2, 20);

   t a;

   for (n = 2; n <= 20; n++)
       a[n] = n;

That 'Silly' array isn't very serious. I got a lot more use out of
Ada's feature of any discrete type being usable as an array index.
This gives you the efficiency of normal arrays but with a huge
readability boost. For an example from last year's Advent of Code:

   N : constant Natural := 32;
   type Rune is ('#', 'G', 'E', '.');
   type Runestring is array (1 .. N) of Rune;
   Maze : array (1 .. N) of Runestring :=
     ("################################",
      "#######..#######..#.G..##..#####",
      "######.....#####.....GG.##.#####",
      ... more of this ...);

   subtype Living_Runes is Rune range 'G' .. 'E';
   Initial_States_Table : constant array (Living_Runes) of 
Unit_State :=
     ('G' => (Hit_Points => 200, Attack_Power => 3, Moved => 
False),
      'E' => (Hit_Points => 200, Attack_Power => 3, Moved => 
False));

So you can refer to Initial_States_Table('G').Hit_Points to know
how healthy the goblins start out in this game.

Thinking of that, I came up with the following D code:

   import std.stdio, core.exception;

   enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, 
Friday, Saturday }

   void main() {
       int[Days] worklog;
       ++worklog[Days.Wednesday];
       writeln("- worklog");
       dumplog(worklog);

       writeln();

       int[Days.max+1] altlog;
       ++altlog[Days.Saturday];
       writeln("- altlog");
       dumplog(altlog);
   }

   void dumplog(T)(T log) {
       foreach (day; Days.Sunday .. Days.Saturday) {
           try {
               writefln("%5d %s", log[day], day);
           }
           catch (core.exception.RangeError e) {}
       }
   }

Which has this output:

   - worklog
       1 Wednesday

   - altlog
       0 Sunday
       0 Monday
       0 Tuesday
       0 Wednesday
       0 Thursday
       0 Friday

And which has these faults, vs. the Ada equivalent:

1. worklog is an associative array and ++worklog[Days.Wednesday]
    compiles to a function call. This is more flexible of course 
but
    here it's unwanted and more expensive than a simple array.

2. worklog needs explicit initialization

3. this is ugly: int[Days.max+1] altlog

4. I can't write foreach (day; Days) { } ?

5. the foreach in that code is wrong: it skips Saturday, and the
    obvious fix of +1 is both ugly and an error:

   Error: cannot implicitly convert expression day of type int to 
Days

This works:

   foreach (day; Days.min .. Days.max+1)
       writefln("%5d %s", log[cast(Days) day], cast(Days) day);

But compare to Ada:

   with Ada.Text_IO; use Ada.Text_IO;

   procedure Enum is
      type Weekdays is
        (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, 
Sunday);

      procedure What_Day (Day : Weekdays) is
      begin
         case Day is
            when Monday | Tuesday | Wednesday | Thursday | Friday 
=>
               Put_Line (Weekdays'Image (Day) & " is a weekday");
            when Saturday | Sunday =>
               Put_Line (Weekdays'Image (Day) & " is a weekend 
day");
         end case;
      end What_Day;
   begin
      for J in Weekdays'Range loop
         What_Day (J);
      end loop;
   end Enum;

... which, OK, has its own problems:

   MONDAY is a weekday
   TUESDAY is a weekday
   WEDNESDAY is a weekday
   THURSDAY is a weekday
   FRIDAY is a weekday
   SATURDAY is a weekend day
   SUNDAY is a weekend day

Is there a nicer way to have enum array keys in D?
Apr 10
parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
 Hello,

 When reading through the following D blog post, I noticed in the
 feature chart that D had "Arrays beginning at arbitrary 
 indices" as
 a +1 feature, the same as in Ada.

 https://dlang.org/blog/2018/06/20/how-an-engineering-company-chose-to-migrate-to-d/

 That surprised me, and from the code with the blog, that seems 
 to be
 generous.

 [...]

 Is there a nicer way to have enum array keys in D?
No. I've myself written my own EnumIndexedArray [1] type. It's pretty simple. Just a couple of operator overload to preovide the syntax. I went from ObjFPC/Delphi which has what you describe from Ada too and missed it. (typically: `enum TStuff = (); var stuffStrings: array[TStuff] of string;` ...) [1] https://github.com/Basile-z/iz/blob/9ce6fc0e2e0c74f97d530ce598a6842b7b048f25/import/iz/enumset.d#L1086
Apr 10
parent reply Julian <julian.fondren gmail.com> writes:
On Thursday, 11 April 2019 at 06:45:23 UTC, Basile B. wrote:
 On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
 Is there a nicer way to have enum array keys in D?
No. I've myself written my own EnumIndexedArray [1] type. It's pretty simple. Just a couple of operator overload to preovide the syntax. I went from ObjFPC/Delphi which has what you describe from Ada too and missed it. (typically: `enum TStuff = (); var stuffStrings: array[TStuff] of string;` ...) [1] https://github.com/Basile-z/iz/blob/9ce6fc0e2e0c74f97d530ce598a6842b7b048f25/import/iz/enumset.d#L1086
Thanks. That still seems like enough work that I'd rather do things the D way. At least if I don't also want Enum sets. That gave me the idea for this though: import std.stdio; struct EnumRange(E) { int begin = E.min; int end = E.max + 1; bool empty() { return begin == end; } void popFront() { ++begin; } E front() { return cast(E) begin; } } enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } void main() { int[Days.max+1] worklog; ++worklog[Days.Saturday]; writeln("- worklog"); EnumRange!(Days) why; foreach (day; why) writefln("%5d %s", worklog[day], day); } Which I'm still disappointed is not: foreach (day; EnumRange!(Days)) Also, this isn't too bad: void main() { int[Days.max+1] worklog; ++worklog[Days.Saturday]; writeln("- worklog"); foreach (day, count; worklog) writefln("%5d %s", count, cast(Days) day); } I don't see a difference in micro-benchmarks. *shrug*
Apr 11
parent Basile B. <b2.temp gmx.com> writes:
On Thursday, 11 April 2019 at 07:56:42 UTC, Julian wrote:
 On Thursday, 11 April 2019 at 06:45:23 UTC, Basile B. wrote:
 On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
I don't see a difference in micro-benchmarks. *shrug*
Your enum is int so in machine code it's exactly like processing a machine word. It's not even worth benchmarking this ;) As a side note, and in case you would not know it yet, there's this traits that's useful with enums: https://dlang.org/phobos/std_traits.html#EnumMembers
Apr 11