You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
2.9 KiB
96 lines
2.9 KiB
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace SBRL.Algorithms
|
|
{
|
|
/// <summary>
|
|
/// Picks random items from a list, according to weights assigned to them.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Higher weights mean that an item is picked more frequently than the other items.
|
|
/// </remarks>
|
|
/// <license>Mozilla Public License version 2.0</license>
|
|
/// <origin>https://gist.github.com/sbrl/9090a8c646b8d34b6e0170ddfd197d09</origin>
|
|
/// <author>Starbeamrainbowlabs (https://starbeamrainbowlabs.com/)</author>
|
|
/// <changelog>
|
|
/// v0.1 - 20th May 2017:
|
|
/// - Creation! :D
|
|
/// v0.2 - 17th Februrary 2018:
|
|
/// - Add Count property
|
|
/// - Add SetContents and ClearContents methods
|
|
/// - Add empty constructor
|
|
/// - Next() will now throw an InvalidOperationException if the generator's internal weights list is empty
|
|
/// </changelog>
|
|
public class WeightedRandom<ItemType>
|
|
{
|
|
private Random rand = new Random();
|
|
|
|
protected ConcurrentDictionary<ItemType, double> weights = new ConcurrentDictionary<ItemType, double>();
|
|
|
|
public int Count {
|
|
get {
|
|
return weights.Count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new weighted random number generator.
|
|
/// </summary>
|
|
/// <param name="items">The dictionary of weights and their corresponding items.</param>
|
|
public WeightedRandom(IDictionary<ItemType, double> items)
|
|
{
|
|
SetContents(items);
|
|
}
|
|
/// <summary>
|
|
/// Createse a new empty weighted random number generator.
|
|
/// Remember to populate it before using!
|
|
/// </summary>
|
|
public WeightedRandom()
|
|
{
|
|
}
|
|
|
|
public void SetContents(IDictionary<ItemType, double> items)
|
|
{
|
|
if (items.Count == 0)
|
|
throw new ArgumentException("Error: The items dictionary provided is empty!");
|
|
|
|
double totalWeight = items.Values.Sum();
|
|
Parallel.ForEach(items, (KeyValuePair<ItemType, double> itemData) => {
|
|
if (!weights.TryAdd(itemData.Key, itemData.Value / totalWeight))
|
|
throw new Exception("WeightedRandom: Failed to add new weight definition to weights ConcurrentDictionary!");
|
|
});
|
|
}
|
|
public void ClearContents()
|
|
{
|
|
weights.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Picks a new random item from the list provided at initialisation, based
|
|
/// on the weights assigned to them.
|
|
/// </summary>
|
|
/// <returns>A random item, picked according to the assigned weights.</returns>
|
|
public ItemType Next()
|
|
{
|
|
if (weights.Count == 0)
|
|
throw new InvalidOperationException("Error: No weights specified! Add some with SetContents() before generating random numbers.");
|
|
double target = rand.NextDouble();
|
|
|
|
double lower = 0;
|
|
double higher = 0;
|
|
foreach (KeyValuePair<ItemType, double> weightData in weights)
|
|
{
|
|
higher += weightData.Value;
|
|
if (target >= lower && target <= higher)
|
|
return weightData.Key;
|
|
lower += weightData.Value;
|
|
}
|
|
|
|
throw new Exception($"Error: Unable to find the weight that matches {target}");
|
|
}
|
|
}
|
|
}
|