www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - iterate over enum name:value pairs

reply "Jay Norwood" <jayn prismnet.com> writes:
In Ali Çehreli very nice book there is this example of iterating 
over enum range which, as he notes, fails

http://ddili.org/ders/d.en/enum.html

   enum Suit { spades, hearts, diamonds, clubs }

     foreach (suit; Suit.min .. Suit.max) {
         writefln("%s: %d", suit, suit);
     }

spades: 0
hearts: 1
diamonds: 2
                ← clubs is missing


It seems like there is interest in iterating over the name:value 
pairs of an enum.  Is this is already available with some D 
programming trick?
Dec 07 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

 In Ali Çehreli very nice book there is this example of 
 iterating over enum range which, as he notes, fails

 http://ddili.org/ders/d.en/enum.html

   enum Suit { spades, hearts, diamonds, clubs }

     foreach (suit; Suit.min .. Suit.max) {
         writefln("%s: %d", suit, suit);
     }

 spades: 0
 hearts: 1
 diamonds: 2
                ← clubs is missing


 It seems like there is interest in iterating over the 
 name:value pairs of an enum.  Is this is already available with 
 some D programming trick?
min and max of enumeratinos are traps, I don't use them. enum Suit { spades = -10, hearts = 10 } Here max and min are worse than useless to list the enumeration members. Also don't define "max" and "min" as members of the enumeration, because this causes a silent silly name clash: enum Suit { spades, hearts, max, min, diamonds, clubs } This is how you usually iterate them: void main() { import std.stdio, std.traits; enum Suit { spades, hearts, diamonds, clubs } foreach (immutable suit; [EnumMembers!Suit]) writefln("%s: %d", suit, suit); } But keep in mind that too has a trap: void main() { import std.stdio, std.traits; enum Suit { spades = 0, hearts = 1, diamonds = 1, clubs = 2 } foreach (immutable suit; [EnumMembers!Suit]) writefln("%s: %d", suit, suit); } Prints: spades: 0 hearts: 1 hearts: 1 clubs: 2 There are cases where the supposed "strong typing" of D is a joke :-) Try to do the same things with Ada enumerations. Bye, bearophile
Dec 07 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
Thanks.  This is exactly what I was looking for.

I tried this iteration below, based on the example shown in the 
std.traits documentation, and the int values are not what I 
expected, but your example works fine.




import std.traits;

void main()
{
     enum Suit { spades, hearts=4, diamonds=10, clubs }

     foreach (i, member; EnumMembers!Suit)
     {
	writefln("%s: %d", member, i);
     }

}


D:\dprojects\ConsoleApp1\ConsoleApp1>dmd -run main.d
spades: 0
hearts: 1
diamonds: 2
clubs: 3

but the same example, substituting writefln("%s: %d", member, 
member) prints

D:\dprojects\ConsoleApp1\ConsoleApp1>dmd -run main.d
spades: 0
hearts: 4
diamonds: 10
clubs: 11
Dec 07 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

     enum Suit { spades, hearts=4, diamonds=10, clubs }

     foreach (i, member; EnumMembers!Suit)
Here 'i' is the index of the enumeration type tuple. This code lacks the [] I added in my code, so your foreach is a static one. To tell them apart when I read the code I sometimes add a comment: /*static*/ foreach (i, member; EnumMembers!Suit) Bye, bearophile
Dec 08 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
I see comments about enums being somehow implemented as tuples, 
and comments about tuples somehow being implemented as structs, 
but I couldn't find examples of static initialization of arrays 
of either.

Finally after playing around with it for a while, it appears this 
example below works for static array of struct initialization.  
It also doesn't display the  enum bug of hearts2 coming back as 
hearts in the iteration.

I tried C static array of struct initialization syntax, and it 
didn't work, which kind of surprises me.

module main;

import std.stdio;

void main()
{
	struct Suit {string nm; int val;};

	static Suit[5] suits =  [
			Suit("spades",1),
			Suit("hearts",4),
			Suit("hearts2",4),
			Suit("diamonds",10),
			Suit("clubs",11)
		];


     foreach (member;  suits)
     {
		writefln("%s: %d", member.nm, member.val);
     }

}

D:\dprojects\ConsoleApp1\ConsoleApp1>dmd -run main.d
spades: 1
hearts: 4
hearts2: 4
diamonds: 10
clubs: 11
Dec 08 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

 I see comments about enums being somehow implemented as tuples,
Enums are usually implemented as ints, unless you specify a different type.
 and comments about tuples somehow being implemented as structs,
Phobos Tuples are implemented with structs.
 but I couldn't find examples of static initialization of arrays 
 of either.
Here it is: import std.typecons; enum Foo { A, B, C } Foo[] a1 = [Foo.A, Foo.B]; alias T = Tuple!(int, int); T[] a1 = [T(1, 2), T(3, 4)]; void main() {}
 It also doesn't display the  enum bug of hearts2 coming back as 
 hearts in the iteration.
Because those are different strings.
 I tried C static array of struct initialization syntax, and it 
 didn't work, which kind of surprises me.
Here it is: void main() { static struct Suit { string nm; int val; } static Suit[5] suits = [ {"spades", 1}, {"hearts", 4}]; } Bye, bearophile
Dec 08 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
Yes, thanks, that syntax does work for the initialization.

The C syntax that failed for me was using the curly brace form 
shown in the following link.

http://www.c4learn.com/c-programming/c-initializing-array-of-structure/

Also, I think I was trying forms of defining the struct and 
initializing the array in the same line... something like this C:
static struct Suit{ int i; long lv;} suits[3] = {{1, 2L},{2, 
4L},{3,9L}};

It looks to me like D requires a named struct definition in a 
separate line from the array  definition.  If that is so, then 
the C initialization of an array with an unnamed struct type, 
like this, would require a struct type name.

static struct { int i; long lv;} suits[3] = {{1, 2L},{2, 
4L},{3,9L}};

So, from your static intialization example, this works.

Also, the conversion of struct to tuple makes the writefln 
tupleof conversion on the struct a little cleaner, since you only 
have to specify the single tuple parameter.


module main;

import std.stdio;

void main()
{
	struct Suit {string nm; int val; int val2; string shortNm;};

	static Suit[5] suits =  [
		{"spades",1,6,"spd"},
		{"hearts",4,10,"hrt"},
		{"hearts2",4,10,"hrt2"},
		{"diamonds",10,16,"dmd"},
		{"clubs",11,17,"clb"}
		];
	
     foreach (member;  suits)
     {
	auto tup = member.tupleof;
	writefln("%s %d %d %s", tup);
     }

}

prints
spades 1 6 spd
hearts 4 10 hrt
hearts2 4 10 hrt2
diamonds 10 16 dmd
clubs 11 17 clb

I also tried using writefln(tup) and writeln(tup) in the example 
above.  The output from writeln(tup) looks like it is headed in 
the right direction.  Maybe a writecsv(tup) would be useful.

spades16spd
hearts410hrt
hearts2410hrt2
diamonds1016dmd
clubs1117clb
Dec 08 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

 If that is so, then the C initialization of an array with an 
 unnamed struct type, like this, would require a struct type 
 name.

 static struct { int i; long lv;} suits[3] = {{1, 2L},{2, 
 4L},{3,9L}};
Giving a struct a name is often a good idea. But if you don't want to name it, then you can use a Phobos Tuple. You can even omit its field names if you want.
 	struct Suit {string nm; int val; int val2; string shortNm;};
Better: static struct Suit {string nm; int val; int val2; string shortNm;} Generally inside functions it's better to define static struct. And struct definitions don't need a trailing semicolon.
     foreach (member;  suits)
Generally it's better to attach an 'immutable' there, this is not always possible, but it's a good habit: foreach (immutable member; suits)
 I also tried using writefln(tup) and writeln(tup) in the 
 example above.  The output from writeln(tup) looks like it is 
 headed in the right direction.  Maybe a writecsv(tup) would be 
 useful.
Try: member.writeln; Bye, bearophile
Dec 08 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
       foreach (immutable member;  suits)
Sometimes you have to use: foreach (const member; suits) Bye, bearophile
Dec 08 2013
prev sibling parent reply "Jay Norwood" <jayn prismnet.com> writes:
On Sunday, 8 December 2013 at 22:30:25 UTC, bearophile wrote:

 Try:

 member.writeln;

 Bye,
 bearophile
yeah, that's pretty nice. module main; import std.stdio; void main() { struct Suit {string nm; int val; int val2; string shortNm;}; static Suit[5] suits = [ {"spades",1,6,"spd"}, {"hearts",4,10,"hrt"}, {"hearts2",4,10,"hrt2"}, {"diamonds",10,16,"dmd"}, {"clubs",11,17,"clb"} ]; foreach (immutable member; suits) { member.writeln(); } } output: immutable(Suit)("spades", 1, 6, "spd") immutable(Suit)("hearts", 4, 10, "hrt") immutable(Suit)("hearts2", 4, 10, "hrt2") immutable(Suit)("diamonds", 10, 16, "dmd") immutable(Suit)("clubs", 11, 17, "clb")
Dec 08 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

     struct Suit {string nm; int val; int val2; string shortNm;};
You have missed my suggestions above regarding the struct :-) Look at this: void main() { int x; struct Foo1 { int bar1() { return x; } } pragma(msg, Foo1.sizeof); static struct Foo2 { // this gives an error int bar2() { return x; } } pragma(msg, Foo2.sizeof); } bar2() gives an error because it can't access x. If you comment out the bar2 line, the output is: 4u 1u Usually you don't want your struct defined inside a function to contain a pointer to the enclosing function.
     static Suit[5] suits =  [
 		{"spades",1,6,"spd"},
 		{"hearts",4,10,"hrt"},
 		{"hearts2",4,10,"hrt2"},
 		{"diamonds",10,16,"dmd"},
 		{"clubs",11,17,"clb"}
 		];
Unless you have to mutate the contents of a variable, like your suits, define it const or immutable: static immutable Suit[5] suits = [ {"spades",1,6,"spd"}, ... ]; Generally in D add const/immutable to all variables that you don't need to mutate, if/where you can. Bye, bearophile
Dec 08 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
    static Suit[5] suits =  [
 		{"spades",1,6,"spd"},
 		{"hearts",4,10,"hrt"},
 		{"hearts2",4,10,"hrt2"},
 		{"diamonds",10,16,"dmd"},
 		{"clubs",11,17,"clb"}
Also, in D it's better to put a space after every comma, to increase readability a little: static immutable Suit[5] suits = [ {"spades", 1, 6, "spd"}, Bye, bearophile
Dec 08 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
It looks like the writeln() does a pretty good job, even for enum 
names.

I also saw a prettyprint example that prints the structure member 
name, and compared its output.
http://forum.dlang.org/thread/ip23ld$93u$1 digitalmars.com


module main;

import std.stdio;
import std.traits;

void main()
{
     enum Suit { spades, hearts=4, diamonds=10, clubs }
     enum SuitShort { spd, hrt=4, dmd=10, clb }

     static struct Suits { Suit nm; int val; int val2;  SuitShort 
shortNm;}

     static Suits[] suits =  [
		{Suit.spades, 1, 6, SuitShort.spd},
		{Suit.hearts, 4, 10, SuitShort.hrt},
		{Suit.diamonds, 4, 10, SuitShort.dmd},
		{Suit.clubs, 10, 16, SuitShort.clb}
	];

     foreach (immutable member;  suits)
     {
	auto fields = __traits(allMembers, typeof(member));
	auto values = member.tupleof;

	foreach (index, value; values)
	{
	    writef("%s=%s, ", fields[index], value);
	}
	writeln();
  	member.writeln();
     }
}


output:
nm=spades, val=1, val2=6, shortNm=spd,
immutable(Suits)(spades, 1, 6, spd)
nm=hearts, val=4, val2=10, shortNm=hrt,
immutable(Suits)(hearts, 4, 10, hrt)
nm=diamonds, val=4, val2=10, shortNm=dmd,
immutable(Suits)(diamonds, 4, 10, dmd)
nm=clubs, val=10, val2=16, shortNm=clb,
immutable(Suits)(clubs, 10, 16, clb)
Dec 08 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

Using enums, despite their problems, if often better than strings.


     static Suits[] suits =  [
 		{Suit.spades, 1, 6, SuitShort.spd},
 		{Suit.hearts, 4, 10, SuitShort.hrt},
 		{Suit.diamonds, 4, 10, SuitShort.dmd},
 		{Suit.clubs, 10, 16, SuitShort.clb}
 	];

     foreach (immutable member;  suits)
In some cases you can also use with() to avoid struct/enum name repetitions (unfortunately it creates a scope, so if you define an immutable variable inside it, it will be invisible and destroyed once the with() scope ends. This reduces the usefulness of with()). with (Suit) with (SuitShort) { static Suits[] suits = [ {spades, 1, 6, spd}, {hearts, 4, 10, hrt}, {diamonds, 4, 10, dmd}, {clubs, 10, 16, clb} ]; foreach (immutable member; suits) ... Bye, bearophile
Dec 08 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
Thanks.  That's looking pretty clean.

I had already tried the shorter enum names without using the with 
statement, and it failed to compile.  I thought it might work 
since the struct definition already specifies the enum type for 
the two members.


     with (Suit) with (SuitShort)
     {
         static Suits[] suits = [
             {spades,    1,  6, spd},
             {hearts,    4, 10, hrt},
             {diamonds,  4, 10, dmd},
             {clubs,    10, 16, clb}
         ];

         foreach (immutable member;  suits)
         ...


 Bye,
 bearophile
Dec 08 2013
parent reply "Jay Norwood" <jayn prismnet.com> writes:
I notice that if Suit and SuitShort have an enum with the same 
name, then you still have to fully qualify the enum names when 
using the with statement.  So, for example, if spd in SuitShort 
was renamed spades, the first entry in the array initialization 
would have to be {Suit.spades, 1, 6, SuitShort.spades}.

    with (Suit) with (SuitShort)
    {
        static Suits[] suits = [
            {spades,    1,  6, spd},
            {hearts,    4, 10, hrt},
            {diamonds,  4, 10, dmd},
            {clubs,    10, 16, clb}
        ];

        foreach (immutable member;  suits)
        ...


 Bye,
 bearophile
Dec 08 2013
parent "bearophile" <bearophileHUGS lycos.com> writes:
Jay Norwood:

 I notice that if Suit and SuitShort have an enum with the same 
 name, then you still have to fully qualify the enum names when 
 using the with statement.  So, for example, if spd in SuitShort 
 was renamed spades, the first entry in the array initialization 
 would have to be {Suit.spades, 1, 6, SuitShort.spades}.
Yes, that's named anti-shadowing, the D type system tried to avoid such bugs in your code. Bye, bearophile
Dec 09 2013