0
\$\begingroup\$

Whilst trying to vary the draw color of some randomly positioned sprites, I really wanted to be able to just supple a table with 2 columns (colour and chance of picking) to some helper function that would do all calculations for me.

Ideally, I want to be able to do something like the follow (semi-pseudocode) :

var table = new object[]
{
    Color.White, 0.2, // Chance: 20%
    Color.Red, 0.2, // Chance: 20%
    Color.Blue, 0.5, // Chance: 50%
    Color.Black, 0, // Default, i.e. remaining chance (10%)
};

Color newColor = Pick<Color>.AtRandom(table); // Apologies if this is very wrong!

I'm fairly confident I could get something to work within the class - my issue is deciding on the correct way to supply the Pick class the chances for each possible selection. I don't want this to be specifically for Color - I want to be able to select between any type of struct / instance of a class. The last line to me doesn't seem 'possible' as in I am unsure whether or not you would require an instance of Pick or if this can be done using a static class.

The use of object[] seems like a hackerish way to implement this. It's more streamlined to use a Tuple however the chance table becomes extremely verbose when every item in the array would be along the lines of Tuple.Create<Color, double>(Color.Red, 0.2), etc.

What is the most sensible / expandable way to do this?

Thanks for your time.

\$\endgroup\$
2
\$\begingroup\$

If you need to create an instance of the table anyway, you might as well build that into your Pick class. That way you can get type safety throughout, no hacky object[] business and boxing/unboxing.

eg.

public class PickTable<T>
{
   List<T> values;
   List<double> weights

   public PickTable(int capacity)
   {
     values = new List<T>(capacity);
     weights = new List<double>(capacity);
   }

   public void Add(T value, double weight)
   {
     values.Add(value);
     weights.Add(weight);
     // Might want to track total weight here, or do reweighting as necessary 
     // (eg. for 0 weight = remainder)
   }

  public T AtRandom()
  {
    // Random selection logic goes here
    return values[selectedIndex];
  }
}

Using it is then just as concise as your version:

var colorTable = new PickTable<Color>(4);
colorTable.Add(Color.White, 0.2);
colorTable.Add(Color.Red, 0.2);
colorTable.Add(Color.Blue, 0.5);
colorTable.Add(Color.Black, 0);

Color newColor = colorTable.AtRandom();

If you're dealing with large tables or picking from them frequently, there's some good tips on sampling arbitrary discrete probability distributions here.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks - this will certainly get me started. I going to expand on it and have a .FromFile() method so I can write it in the form I want! \$\endgroup\$ – Joe Shanahan Dec 23 '13 at 14:17
  • \$\begingroup\$ Sounds like a good plan. Keep in mind your weights don't necessarily need to be probabilities that sum to 1 - by keeping track of a total, you can just use relative weights. I find that makes it easier for iteration because I can arbitrarily increase a weight without manually reducing all the others, and it also makes the code more robust against any sloppiness in the data. \$\endgroup\$ – DMGregory Dec 23 '13 at 14:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.