Search code examples
d

Select a random element of an enum in D


I have taken to selecting random values from enums like so:

import std.random : uniform;
import std.stdio : writefln;
import std.conv;

enum E {A, B, C}

int main(){
    auto select = cast(E)uniform(to!int(E.min), to!int(E.max));
    writefln("select %s", select);
    return 0;
}

This is surprisingly verbose, and prone to issues if any enum members take values outside the default (or larger than int).

Ideally I would take a range that represents the elements of the enum, and provide this to randomSample. However, this doesn't appear to be possible.

Is there a more idiomatic way to select a random value from an enum in D?

EDIT:

Using the answer provided by fwend, here is a template function that achieves what I want:

T RandomEnumElement(T)() if (is(T == enum)){
    auto members = [EnumMembers!T];
    return members[(uniform(0, members.length))];
}

Solution

  • import std.random : uniform;
    import std.stdio : writefln;
    import std.conv;
    import std.traits;
    
    enum E {A, B, C}
    
    int main(){
        auto select = [EnumMembers!E][uniform(0, 3)];
        writefln("select %s", select);
        return 0;
    }
    

    Edit: if you need to use the enum values more than once, you can store them in a static immutable array first, otherwise the array will be built every time. That also allows you to get rid of the magic number 3.

    (...)
    int main(){
        static immutable Evalues = [EnumMembers!E];
        auto select1 = Evalues[uniform(0, Evalues.length)];
        writefln("select %s", select1);
    
        auto select2 = Evalues[uniform(0, Evalues.length)];
        writefln("select %s", select2);
        return 0;
    }
    

    Edit 2: As pointed out by Idan Arye, the template could be even terser:

    T RandomEnumElement(T)() if (is(T == enum)){
        return [EnumMembers!T][(uniform(0, $))];
    }
    

    Edit 3: tgehr has suggested the following solution, which would build the lookup table once at compile time and avoid GC allocation altogether:

    T RandomEnumElement(T)() if (is(T == enum)) {
        static immutable members = [EnumMembers!T];
        return members[uniform(0, $)];
    }