namespace GamblingCoin; public class WeightedRandomExecutor { private readonly List _actions = new(); private readonly Random _random = new(); private double _totalWeight; /// /// Adds a function to be potentially executed, along with its weight. /// /// The function to add. It must accept an argument of type TEvent. /// The positive weight for this function. Higher weights mean higher probability. public WeightedRandomExecutor AddAction(Action action, double weight) { var weightedAction = new WeightedAction(action, weight); _actions.Add(weightedAction); _totalWeight += weight; return this; } /// /// Executes one of the added functions randomly based on their weights. /// The chosen function will be called with the provided 'ev' argument. /// /// The event argument to pass to the chosen function. /// Thrown if no actions have been added. 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 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 Action { get; } public double Weight { get; } } }