using System; using System.Collections.Generic; using System.Linq; namespace AibisDream.Framework { public class SingleSelector : ISelector { private readonly List _items; private readonly List _prefixSums; private readonly int _totalWeight; private readonly Random _random; #region 暂时无需实现 public void AddItem(T item) { throw new NotImplementedException(); } public void RemoveItem(T item) { throw new NotImplementedException(); } public int Count => _items.Count; #endregion public SingleSelector(IEnumerable items, Func weightSelector) { if (items == null) throw new ArgumentNullException(nameof(items)); if (weightSelector == null) throw new ArgumentNullException(nameof(weightSelector)); var itemList = items.ToList(); _items = new List(itemList.Count); _prefixSums = new List(itemList.Count); int sum = 0; foreach (var item in itemList) { int weight = weightSelector(item); if (weight < 0) throw new ArgumentException("Weights cannot be negative", nameof(items)); if (weight > 0) { sum += weight; _prefixSums.Add(sum); _items.Add(item); } } _totalWeight = sum; _random = new Random(); } public T Select() { if (_totalWeight == 0) throw new InvalidOperationException("All weights are zero"); int randomValue = _random.Next(0, _totalWeight); int index = BinarySearch(randomValue); return _items[index]; } private int BinarySearch(int value) { int left = 0; int right = _prefixSums.Count - 1; while (left < right) { int mid = left + (right - left) / 2; if (_prefixSums[mid] <= value) { left = mid + 1; } else { right = mid; } } return left; } } public class BatchSelector : ISelector { private readonly List _items; private readonly List _batchWeight; private readonly int _totalWeight; private readonly Random _random; private int _currentWeight; private List _curBatchWeight; #region 暂时无需实现 public void AddItem(T item) { throw new NotImplementedException(); } public void RemoveItem(T item) { throw new NotImplementedException(); } #endregion public int Count => _totalWeight; public BatchSelector(IEnumerable items, Func weightSelector) { if (items == null) throw new ArgumentNullException(nameof(items)); if (weightSelector == null) throw new ArgumentNullException(nameof(weightSelector)); var itemList = items.ToList(); _items = new List(itemList.Count); _batchWeight = new List(itemList.Count); int sum = 0; foreach (var item in itemList) { int weight = weightSelector(item); if (weight < 0) throw new ArgumentException("Weights cannot be negative", nameof(items)); if (weight > 0) { sum += weight; _batchWeight.Add(weight); _items.Add(item); } } _curBatchWeight = new List(_batchWeight); _totalWeight = sum; _currentWeight = sum; _random = new Random(); if (_totalWeight == 0) { // 出错了 throw new InvalidOperationException("All weights are zero"); } } public T Select() { // 如果已经抽完就重置 if (_currentWeight == 0) { ResetSelector(); } int randomValue = _random.Next(0, _currentWeight); int index = BinarySearch(randomValue); // 选择后减去权重 _currentWeight -= 1; _curBatchWeight[index] -= 1; return _items[index]; } private void ResetSelector() { _currentWeight = _totalWeight; _curBatchWeight = new List(_batchWeight); } private int BinarySearch(int value) { var sum = 0; for (var i = 0; i < _curBatchWeight.Count; i++) { sum += _curBatchWeight[i]; if (sum >= value) { return i; } } return _curBatchWeight.Count - 1; } } public class LimitedSelector : ISelector { private readonly List _items; private readonly Random _random; public LimitedSelector(IEnumerable items) { _items = items.ToList(); _random = new Random(); } public T Select() { if (_items.Count == 0) { throw new InvalidOperationException("Selector is Empty"); } var res = _items[_random.Next(0, _items.Count)]; _items.Remove(res); return res; } public int Count => _items.Count; } public class UnRepeatSelector : ISelector { private readonly List _items; private readonly Random _random; private T _lastCache; public int Count => _items.Count; public UnRepeatSelector(IEnumerable items) { _items = items.ToList(); _random = new Random(); } public T Select() { if (_items.Count == 0) { throw new InvalidOperationException("Selector is Empty"); } T res; do { res = _items[_random.Next(0, _items.Count)]; } while (Equals(res, _lastCache)); _lastCache = res; return res; } } public interface ISelector { T Select(); int Count { get; } } }