Reputation: 199
I have a bunch of items than range in size from 1-10
.
I would like to make the size that the item is to be determined by the percentage or chance of the object being that size..
For example:
Items chance of being size 1
= 50%
chance
Items chance of being size 5
= 20%
chance
Items chance of being size 10
= 5%
chance
I know I would need to use a Random
generator for this of course.
But just wondering how would some of you go about the logic of this in C#?
Upvotes: 14
Views: 34878
Reputation: 11
I end using this class of Oleksandr Martysh, modified for generic types and specifically for Unity, in case someone see this usefull :)
public class ProportionalRandomSelector<T> {
private readonly Dictionary<T, int> percentageItemsDict;
public ProportionalRandomSelector() => percentageItemsDict = new();
public void AddPercentageItem(T item, int percentage) => percentageItemsDict.Add(item, percentage);
public T SelectItem() {
// Calculate the summa of all portions.
int poolSize = 0;
foreach (int i in percentageItemsDict.Values) {
poolSize += i;
}
// Get a random integer from 1 to PoolSize.
int randomNumber = Random.Range(1, poolSize);
// Detect the item, which corresponds to current random number.
int accumulatedProbability = 0;
foreach (KeyValuePair<T, int> pair in percentageItemsDict) {
accumulatedProbability += pair.Value;
if (randomNumber <= accumulatedProbability)
return pair.Key;
}
return default; // this code will never come while you use this programm right :)
}
}
//Example of use. You can use any type for the item too, and don't need an internal struct for the use.
public class Behaviour : MonoBehaviour {
ProportionalRandomSelector<string> randomSelector = new();
randomSelector.AddPercentageItem("Option1", 20);
randomSelector.AddPercentageItem("Option2", 30);
randomSelector.AddPercentageItem("Option3", 30);
randomSelector.AddPercentageItem("Option4", 15);
randomSelector.AddPercentageItem("Option5", 5);
string result = randomSelector.SelectItem();
}
Upvotes: 1
Reputation: 413
I did it this way, maybe it will be useful for others.
public class Product
{
public int Id { get; set; }
public int Name { get; set; }
public int Percent { get; set; }
}
public class ChoiceItemModel
{
public int Id { get; set; }
public int Percent { get; set; }
public int Min { get; set; }
public int Max { get; set; }
}
public int ChoiceProduct(List<Product> products)
{
var chioiceItems = new List<ChoiceItemModel>();
var percent = 0;
foreach (var product in products.OrderByDescending(p => p.Percent).ToList())
{
chioiceItems.Add(new ChoiceItemModel()
{
Id = product.Id,
Percent = product.Percent,
Min = percent,
Max = product.Percent + percent
});
percent = product.Percent + percent; //max
}
var random = new Random();
var probability = random.Next(1, 100);
var found = chioiceItems.FirstOrDefault(p => probability > p.Min && probability <= p.Max);
if (found != null)
return found.Id;
}
Upvotes: 0
Reputation: 186803
First of all: the probabilities provided don't add up to 100%:
50% + 20% + 5% = 75%
So you have to check these values. You may want to generate these per cents:
// Simplest, but not thread safe
private static Random s_Random = new Random();
...
int perCent = s_Random.Next(0, 100);
if (perCent < 50) // 0 .. 49
{
// return Item of size 1
}
else if (perCent < 50 + 20) // 50 .. 69
{
// return Item of size 5
}
else if (perCent < 50 + 20 + 5) // 70 .. 74
{
// return Item of size 10
}
...
Upvotes: 22
Reputation: 190
Use my method. It is simple and easy-to-understand. I don't count portion in range 0...1, i just use "Probabilityp Pool" (sounds cool, yeah?) I make a list with all elements i want choose from. Every element has its own chance. It is useful to set the most common element chance = 100, so most rare elements would be 60 or 50.
At circle diagram you can see weight of every element in pool
Here you can see an implementing of accumulative probability for roulette
`
// Some c`lass or struct for represent items you want to roulette
public class Item
{
public string name; // not only string, any type of data
public int chance; // chance of getting this Item
}
public class ProportionalWheelSelection
{
public static Random rnd = new Random();
// Static method for using from anywhere. You can make its overload for accepting not only List, but arrays also:
// public static Item SelectItem (Item[] items)...
public static Item SelectItem(List<Item> items)
{
// Calculate the summa of all portions.
int poolSize = 0;
for (int i = 0; i < items.Count; i++)
{
poolSize += items[i].chance;
}
// Get a random integer from 0 to PoolSize.
int randomNumber = rnd.Next(0, poolSize) + 1;
// Detect the item, which corresponds to current random number.
int accumulatedProbability = 0;
for (int i = 0; i < items.Count; i++)
{
accumulatedProbability += items[i].chance;
if (randomNumber <= accumulatedProbability)
return items[i];
}
return null; // this code will never come while you use this programm right :)
}
}
// Example of using somewhere in your program:
static void Main(string[] args)
{
List<Item> items = new List<Item>();
items.Add(new Item() { name = "Anna", chance = 100});
items.Add(new Item() { name = "Alex", chance = 125});
items.Add(new Item() { name = "Dog", chance = 50});
items.Add(new Item() { name = "Cat", chance = 35});
Item newItem = ProportionalWheelSelection.SelectItem(items);
}
Upvotes: 10