using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using UnityEngine; namespace XericLibrary.Runtime.MacroLibrary { public static partial class MacroMath { #region 洗牌算法 /// /// Fisher-Yates洗牌算法,时间复杂度为O(n*n),空间复杂度为O(n) /// /// 待选项目的索引长度 /// 输出项目的索引长度 /// public static List FisherYatesShuffle(int length, int select) { if(select > length) throw new ArgumentOutOfRangeException("在进行洗牌时,要求输出的项目比拥有的项目更多,这是不合理的。"); var random = new System.Random(); List arr = Enumerable.Range(0, length).ToList(); List res = new List(); for(int i = 0; i < select; ++i) { int k = random.Next(arr.Count); res.Add(arr[k]); arr.RemoveAt(k); } return res; } /// /// 蓄水池抽样算法,可以在线程安全的情况下完成洗牌。 /// /// /// 需要抽取的样本 M /// /// public static void ReservoirSampling(List arr, int select, ConcurrentBag reservoir, CancellationToken cancellationToken) { /** * int M = 3; // 需要抽样的元素个数 * int totalElements = 10; // 输入数据的总元素个数 * var arr = new List(); * * for (int i = 1; i <= totalElements; i++) * { * arr.Add(i); * } * * ConcurrentBag reservoir = new ConcurrentBag(); * Random random = new Random(); * * var cancellationTokenSource = new CancellationTokenSource(); * var cancellationToken = cancellationTokenSource.Token; * * ThreadPool.QueueUserWorkItem(state => * ReservoirSampling(arr, M, reservoir, random, cancellationToken); * , null); * * // 停止抽样 * cancellationTokenSource.Cancel(); */ var random = new System.Random(); for(int i = select; i < arr.Count; ++i) { if(cancellationToken.IsCancellationRequested) return; int k = random.Next(i + 1); // 生成一个随机数 k,范围是 [0, i] if(k < select) { // 如果 k 小于 M,将 arr[k] 添加到抽样结果中 reservoir.Add(arr[k]); } } } #endregion #region 应用算法 /// /// 获取列表中随机顺序的对象 /// /// /// /// 需要采样的数量 /// public static IEnumerable GetRandom(this List list, int count = -1) { var index = FisherYatesShuffle(list.Count, count <= 0 ? list.Count : count); for(int i = 0; i < index.Count; i++) yield return list[index[i]]; } #endregion } }