digitalmars.D.learn - Pick a class at random
- axricard (111/111) Jan 03 I have an interface that is implemented by many classes, and I
- H. S. Teoh (68/74) Jan 03 I would tag each implementation with a compile-time enum and use
- axricard (2/17) Jan 04 That works very well, thank you !!
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
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
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:That works very well, thank you !!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
Jan 04