www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Pick a class at random

reply axricard <axelrwiko gmail.com> writes:
I have an interface that is implemented by many classes, and I 
want to pick one of these implementations at random. There are 
two more constraints : first the distribution is not uniform, all 
classes can define the chance they have to be picked (this is 
reflected by the function 'weight()' below). And all classes are 
not always available, this depends on some runtime information.

I went to this minimal working sample, but it seems pretty heavy 
(especially because of the intermediate enum I think) and I feel 
like it could be way prettier and more understandable (maybe by 
using template mixins / compile time manipulations ?).

Do you have any hint on how to improve this ?

```
import std.stdio;
import std.array;
import std.algorithm;
import std.conv;
import std.random;

interface Parent
{
     void doWork();
     static int weight();

     static Parent from(Implem impl)
     {
         final switch (impl)
         {
         case Implem.IMPLEM_1:
             return new Implem1();
         case Implem.IMPLEM_2:
             return new Implem2();
         case Implem.IMPLEM_3:
             return new Implem3();
         }
     }
}

class Implem1 : Parent
{
     void doWork()
     {
         writeln("From Implem 1");
     }

     static int weight()
     {
         return 3;
     }
}

class Implem2 : Parent
{
     void doWork()
     {
         writeln("From Implem 2");
     }

     static int weight()
     {
         return 2;
     }
}

class Implem3 : Parent
{
     void doWork()
     {
         writeln("From Implem 3");
     }

     static int weight()
     {
         return 3;
     }
}

enum Implem
{
     IMPLEM_1,
     IMPLEM_2,
     IMPLEM_3
};

Implem[] availableImplems()
{
     bool runtimeCondition = true;
     if (runtimeCondition)
         return [Implem.IMPLEM_1, Implem.IMPLEM_2];
     else
         return [Implem.IMPLEM_2, Implem.IMPLEM_3];
}

int getWeight(Implem implem)
{
     final switch (implem)
     {
     case Implem.IMPLEM_1:
         return Implem1.weight();
     case Implem.IMPLEM_2:
         return Implem2.weight();
     case Implem.IMPLEM_3:
         return Implem3.weight();
     }
}

int[] getAllWeights(in Implem[] availableImplems)
{
     return availableImplems.map!(implem => 
implem.getWeight()).array;
}

Parent drawAtRandom()
{
     const Implem[] available = availableImplems();
     const int[] weights = getAllWeights(available);
     const Implem drawn = available[dice(weights)];
     return Parent.from(drawn);
}

void main()
{
     Parent p = drawAtRandom();
     p.doWork();
}
```
Jan 03
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Jan 03, 2024 at 04:50:57PM +0000, axricard via Digitalmars-d-learn
wrote:
 I have an interface that is implemented by many classes, and I want to
 pick one of these implementations at random. There are two more
 constraints : first the distribution is not uniform, all classes can
 define the chance they have to be picked (this is reflected by the
 function 'weight()' below).  And all classes are not always available,
 this depends on some runtime information.
I would tag each implementation with a compile-time enum and use compile-time introspection with CRTP[1] to auto-generate the code for choosing a class according to the desired distribution. [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern Something like this: ----------SNIP----------- import std.stdio; interface MyIntf { void work(); } struct ImplemInfo { int weight; MyIntf function() instantiate; } ImplemInfo[] implems; // list of implementations int totalWeight; MyIntf chooseImplem() { import std.random; auto pick = uniform(0, totalWeight); auto slice = implems[]; assert(slice.length > 0); while (slice[0].weight <= pick) { pick -= slice[0].weight; slice = slice[1 .. $]; } return slice[0].instantiate(); } // Base class that uses CRTP to auto-register implementations in // .implems without needing too much boilerplate in every // subclass. class Base(C) : MyIntf { // Derived class must define a .weight member readable // at compile-time. static assert(is(typeof(C.weight) : int), "Derived class must define .weight"); static this() { implems ~= ImplemInfo(C.weight, () { return cast(MyIntf) new C; }); totalWeight += C.weight; } // Derived classes must implement this abstract void work(); } // These classes can be anywhere class Implem1 : Base!Implem1 { enum weight = 1; override void work() { writeln(typeof(this).stringof); } } class Implem2 : Base!Implem2 { enum weight = 2; override void work() { writeln(typeof(this).stringof); } } class Implem3 : Base!Implem3 { enum weight = 3; override void work() { writeln(typeof(this).stringof); } } void main() { // pipe output of program to `sort | uniq -c` to verify that the // required distribution is generated correctly. foreach (_; 0 .. 100) { auto impl = chooseImplem(); impl.work(); } } ----------SNIP----------- --T
Jan 03
parent axricard <axelrwiko gmail.com> writes:
On Wednesday, 3 January 2024 at 17:44:00 UTC, H. S. Teoh wrote:
 On Wed, Jan 03, 2024 at 04:50:57PM +0000, axricard via 
 Digitalmars-d-learn wrote:
 I have an interface that is implemented by many classes, and I 
 want to pick one of these implementations at random. There are 
 two more constraints : first the distribution is not uniform, 
 all classes can define the chance they have to be picked (this 
 is reflected by the function 'weight()' below).  And all 
 classes are not always available, this depends on some runtime 
 information.
I would tag each implementation with a compile-time enum and use compile-time introspection with CRTP[1] to auto-generate the code for choosing a class according to the desired distribution. [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
That works very well, thank you !!
Jan 04