256 lines
6.6 KiB
C#
256 lines
6.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace AibisDream.Framework
|
|
{
|
|
public class SingleSelector<T> : ISelector<T>
|
|
{
|
|
private readonly List<T> _items;
|
|
private readonly List<int> _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<T> items, Func<T, int> weightSelector)
|
|
{
|
|
if (items == null) throw new ArgumentNullException(nameof(items));
|
|
if (weightSelector == null) throw new ArgumentNullException(nameof(weightSelector));
|
|
|
|
var itemList = items.ToList();
|
|
_items = new List<T>(itemList.Count);
|
|
_prefixSums = new List<int>(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<T> : ISelector<T>
|
|
{
|
|
private readonly List<T> _items;
|
|
private readonly List<int> _batchWeight;
|
|
private readonly int _totalWeight;
|
|
private readonly Random _random;
|
|
|
|
private int _currentWeight;
|
|
private List<int> _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<T> items, Func<T, int> weightSelector)
|
|
{
|
|
if (items == null) throw new ArgumentNullException(nameof(items));
|
|
if (weightSelector == null) throw new ArgumentNullException(nameof(weightSelector));
|
|
|
|
var itemList = items.ToList();
|
|
_items = new List<T>(itemList.Count);
|
|
_batchWeight = new List<int>(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<int>(_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<int>(_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<T> : ISelector<T>
|
|
{
|
|
private readonly List<T> _items;
|
|
private readonly Random _random;
|
|
|
|
public LimitedSelector(IEnumerable<T> 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<T> : ISelector<T>
|
|
{
|
|
private readonly List<T> _items;
|
|
private readonly Random _random;
|
|
|
|
private T _lastCache;
|
|
|
|
public int Count => _items.Count;
|
|
|
|
public UnRepeatSelector(IEnumerable<T> 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<out T>
|
|
{
|
|
T Select();
|
|
int Count { get; }
|
|
}
|
|
} |