1

I got an arraylist of objects. Each objects got 2 values, content and weight. content is just a simple string while weight decides the percentage of how often the string should be used.

If I have 2 objects in the array:

Object 1: content: hello weight: 20

Object 2: content: hey weight: 80

First objects content should be used 20% of times while the second objects content should be used 80% of all times.

How would I decide which content gets selected based on the weight?

I got the following code so far:

foreach (ContentModels cont in match_array)
{
     if (match_array.Count == 1)
     {
         match = cont.content;
         break;
     } 
 }

which pretty much selects the first content if the arraylist only contains one object but I don't know what do to if there are more than one objects?

Muki
  • 25
  • 2
  • 8

1 Answers1

1

Alright, I wanted to take a stab at this without looking at someone else's code. Here's what I came up with.

Btw, I hope it was the Java tag that was in error and not the C# tag :D.

Here's the entire program. What follows below is an explanation of each piece

.NET Fiddle

I chose to take each element to be a portion in a ratio. Therefore, in your example, your total is 100 (20 + 80) meaning that the 20 content model should get chosen 20% of the time. If you want to constrain your content models such that their total weights add up to 100, that should be done at the time that you create them.

So here's my solution.
First the content models:

class ContentModel
{
    public string Content { get; set; }
    public int Weight { get; set; }
}

And then a list of test cases:

static List<ContentModel> contentOptions = new List<ContentModel>
{
    new ContentModel
    {
        Content = "hello",
        Weight = 20
    },
    new ContentModel
    {
        Content = "hey",
        Weight = 80
    },
    new ContentModel
    {
        Content = "yo dawg",
        Weight = 90
    }
};

Given these test cases we would expect to see 'Hello' appear about 10.5% of the time (20 / (80 + 90 + 20)) * 100. And so on for the rest of the test cases.

Here's the generator that makes that happen:

Here all we're going to do is figure out what the total weight is that we're working with. Then we're going to pick a random number and go through each model asking "Is this number from this content model?" If no, then subtract that content model's weight and move to the next one until we get to a model where the selection - weight is < 0. In this case we have the model that was chosen. I hope that makes sense.

(Note: I chose to recalculate the total weight every time in case you change the source list of options. If you make that list readonly, then you could move that .Sum() call outside the while loop.)

static IEnumerable<string> GetGreetings()
{
    Random generator = new Random();

    while (true)
    {
        int totalWeight = contentOptions.Sum(x => x.Weight);
        int selection = generator.Next(0, totalWeight);
        foreach (ContentModel model in contentOptions)
        {
            if (selection - model.Weight > 0)
                selection -= model.Weight;
            else
            {
                yield return model.Content;
                break;
            }
        }
     }
}

And finally, here's the main method which will also test this whole thing:

static void Main(string[] args)
{
    List<string> selectedGreetings = new List<string>();

    /* This will get 1000 greetings, 
     * which are the Content property of the models, group them by the greeting,
     * count them, and then print the count along with the greeting to the Console.
     */
    GetGreetings()
        .Take(1000)
        .GroupBy(x => x)
        .Select(x => new { Count = x.Count(), Content = x.Key })
        .ToList()
        .ForEach(x => Console.WriteLine("{0} : {1}", x.Content, x.Count));

    Console.ReadLine();
}

Here are my results from a run through:

Results

Grace Atwood
  • 172
  • 10
  • 1
    Such a detailed post, thanks a lot mate! – Muki May 14 '16 at 18:02
  • 1
    Thank's a lot, this is a really good answer. Only minor thing I would change is put the `int totalWeight = contentOptions.Sum(x => x.Weight);` outside of the while loop, since that value never changes and doesn't have to be re-calculated. – Florian Wolf Jun 25 '20 at 23:13
  • 1
    I adapted your approach into a general extention method using dictionaries ...and gave you the deserved credit as well :) https://stackoverflow.com/a/62600449/11257746 – Florian Wolf Jun 27 '20 at 12:17