/// <summary> /// Builds StaticRandomSelector & clears internal buffers. Must be called after you finish Add-ing items. /// </summary> /// <param name="seed">Seed for random selector. If you leave it -1, the internal random will generate one.</param> /// <returns>Returns IRandomSelector, underlying objects are either StaticRandomSelectorLinear or StaticRandomSelectorBinary. Both are non-mutable.</returns> public IRandomSelector <T> Build(int seed = -1) { T[] items = itemBuffer.ToArray(); float[] CDA = weightBuffer.ToArray(); itemBuffer.Clear(); weightBuffer.Clear(); RandomMath.BuildCumulativeDistribution(CDA); if (seed == -1) { seed = random.Next(); } // RandomMath.ArrayBreakpoint decides where to use Linear or Binary search, based on internal buffer size // if CDA array is smaller than breakpoint, then pick linear search random selector, else pick binary search selector if (CDA.Length < RandomMath.ArrayBreakpoint) { return(new StaticRandomSelectorLinear <T>(items, CDA, seed)); } else { // bigger array sizes need binary search for much faster lookup return(new StaticRandomSelectorBinary <T>(items, CDA, seed)); } }
/// <summary> /// Re/Builds internal CDL (Cummulative Distribution List) /// Must be called after modifying (calling Add or Remove), or it will break. /// Switches between linear or binary search, depending on which one will be faster. /// Might generate some garbage (list resize) on first few builds. /// </summary> /// <param name="seed">You can specify seed for internal random gen or leave it alone</param> /// <returns>Returns itself</returns> public IRandomSelector <T> Build(int seed = -1) { if (itemsList.Count == 0) { throw new Exception("Cannot build with no items."); } // clear list and then transfer weights CDL.Clear(); for (int i = 0; i < weightsList.Count; i++) { CDL.Add(weightsList[i]); } RandomMath.BuildCumulativeDistribution(CDL); // default behavior // if seed wasn't specified (it is seed==-1), keep same seed - avoids garbage collection from making new random if (seed != -1) { // input -2 if you want to randomize seed if (seed == -2) { seed = random.Next(); random = new Random(seed); } else { random = new Random(seed); } } // RandomMath.ListBreakpoint decides where to use Linear or Binary search, based on internal buffer size // if CDL list is smaller than breakpoint, then pick linear search random selector, else pick binary search selector if (CDL.Count < RandomMath.ListBreakpoint) { selectFunction = RandomMath.SelectIndexLinearSearch; } else { selectFunction = RandomMath.SelectIndexBinarySearch; } return(this); }
/// <summary> /// Test and compare linear and binary searches, they should return identical results /// </summary> /// <returns></returns> bool TestEqualityOfLinearVsBinarySearch() { var random = new System.Random(); for (int i = 0; i < 1000000; i++) { float u = i / 999999f; float r = (float)random.NextDouble(); float[] randomWeights = RandomMath.RandomWeightsArray(random, 33); RandomMath.BuildCumulativeDistribution(randomWeights); if (randomWeights.SelectIndexLinearSearch(1f) != randomWeights.SelectIndexBinarySearch(1f)) { return(false); } if (randomWeights.SelectIndexLinearSearch(u) != randomWeights.SelectIndexBinarySearch(u)) { Debug.Log("Not matching u"); Debug.Log(u); Debug.Log(randomWeights.SelectIndexLinearSearch(u)); Debug.Log(randomWeights.SelectIndexBinarySearch(u)); return(false); } if (randomWeights.SelectIndexLinearSearch(r) != randomWeights.SelectIndexBinarySearch(r)) { Debug.Log("Not matching r"); Debug.Log(r); Debug.Log(randomWeights.SelectIndexLinearSearch(r)); Debug.Log(randomWeights.SelectIndexBinarySearch(r)); return(false); } } return(true); }
// time both searches (linear and binary (log)), and find optimal breakpoint - where to use which for maximal performance int FindOptimalBreakpointArray() { int optimalBreakpoint = 2; var random = new System.Random(); Stopwatch stopwatchLinear = new Stopwatch(); Stopwatch stopwatchBinary = new Stopwatch(); float lin = 0f; float log = 1f; // continue increasing "optimalBreakpoint" until linear becomes slower than log // result is around 15-16, varies a bit due to random nature of test while (lin <= log) { int numOfDiffArrays = 100; int numOfTestPerArr = 10000; // u = uniform grid, r = uniform random float u, r; ///Linear Search stopwatchLinear.Stop(); stopwatchLinear.Reset(); float[] items = RandomMath.IdentityArray(optimalBreakpoint); float selectedItem; //here just to simulate selecting from array float[] arr = new float[optimalBreakpoint]; for (int k = 0; k < numOfDiffArrays; k++) { RandomMath.RandomWeightsArray(ref arr, random); RandomMath.BuildCumulativeDistribution(arr); stopwatchLinear.Start(); for (int i = 0; i < numOfTestPerArr; i++) { u = i / (numOfTestPerArr - 1f); selectedItem = items[arr.SelectIndexLinearSearch(u)]; r = (float)random.NextDouble(); selectedItem = items[arr.SelectIndexLinearSearch(r)]; } stopwatchLinear.Stop(); } lin = stopwatchLinear.ElapsedMilliseconds; /// Binary Search stopwatchBinary.Stop(); stopwatchBinary.Reset(); for (int k = 0; k < numOfDiffArrays; k++) { RandomMath.RandomWeightsArray(ref arr, random); RandomMath.BuildCumulativeDistribution(arr); stopwatchBinary.Start(); for (int i = 0; i < numOfTestPerArr; i++) { u = i / (numOfTestPerArr - 1f); selectedItem = items[arr.SelectIndexBinarySearch(u)]; r = (float)random.NextDouble(); selectedItem = items[arr.SelectIndexBinarySearch(r)]; } stopwatchBinary.Stop(); } log = stopwatchBinary.ElapsedMilliseconds; optimalBreakpoint++; } return(optimalBreakpoint); }