2025-06-05 01:12:55 +02:00

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; }
}
}