75 lines
2.7 KiB
C#
75 lines
2.7 KiB
C#
namespace GamblingCoin;
|
|
|
|
public class WeightedRandomExecutor<TEvent>
|
|
{
|
|
private readonly List<WeightedAction> _actions = new();
|
|
private readonly Random _random = new();
|
|
private double _totalWeight;
|
|
|
|
/// <summary>
|
|
/// Adds a function to be potentially executed, along with its weight.
|
|
/// </summary>
|
|
/// <param name="action">The function to add. It must accept an argument of type TEvent.</param>
|
|
/// <param name="weight">The positive weight for this function. Higher weights mean higher probability.</param>
|
|
public WeightedRandomExecutor<TEvent> AddAction(Action<TEvent> action, double weight)
|
|
{
|
|
var weightedAction = new WeightedAction(action, weight);
|
|
_actions.Add(weightedAction);
|
|
_totalWeight += weight;
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes one of the added functions randomly based on their weights.
|
|
/// The chosen function will be called with the provided 'ev' argument.
|
|
/// </summary>
|
|
/// <param name="ev">The event argument to pass to the chosen function.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown if no actions have been added.</exception>
|
|
public void Execute(TEvent ev)
|
|
{
|
|
if (_actions.Count == 0)
|
|
throw new InvalidOperationException(
|
|
"No actions have been added to execute."
|
|
);
|
|
|
|
if (_totalWeight <= 0) // Should not happen if AddAction validates weight > 0
|
|
throw new InvalidOperationException(
|
|
"Total weight is zero or negative, cannot execute."
|
|
);
|
|
|
|
var randomNumber = _random.NextDouble() * _totalWeight;
|
|
double cumulativeWeight = 0;
|
|
|
|
foreach (var weightedAction in _actions)
|
|
{
|
|
cumulativeWeight += weightedAction.Weight;
|
|
if (!(randomNumber < cumulativeWeight)) continue;
|
|
weightedAction.Action(ev);
|
|
return; // Exit after executing one action
|
|
}
|
|
|
|
// Fallback in case of floating point inaccuracies,
|
|
// or if somehow randomNumber was exactly _totalWeight (NextDouble is < 1.0)
|
|
// This should ideally pick the last item if all weights were summed up.
|
|
if (_actions.Any()) _actions.Last().Action(ev);
|
|
}
|
|
|
|
private class WeightedAction
|
|
{
|
|
public WeightedAction(Action<TEvent> action, double weight)
|
|
{
|
|
if (weight <= 0)
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(weight),
|
|
"Weight must be positive."
|
|
);
|
|
|
|
Action = action ?? throw new ArgumentNullException(nameof(action));
|
|
Weight = weight;
|
|
}
|
|
|
|
public Action<TEvent> Action { get; }
|
|
public double Weight { get; }
|
|
}
|
|
} |