Thread overview
Pick a class at random
Jan 03
axricard
Jan 04
axricard
January 03

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();
}
January 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
January 04
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 !!