using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Numerics; using Robust.Shared.Collections; using Robust.Shared.Maths; using Robust.Shared.Utility; namespace Robust.Shared.Random; public static class RandomExtensions { /// /// Generate a random number from a normal (gaussian) distribution. /// /// The random object to generate the number from. /// The average or "center" of the normal distribution. /// The standard deviation of the normal distribution. public static double NextGaussian(this IRobustRandom random, double μ = 0, double σ = 1) { // https://stackoverflow.com/a/218600 var α = random.NextDouble(); var β = random.NextDouble(); var randStdNormal = Math.Sqrt(-2.0 * Math.Log(α)) * Math.Sin(2.0 * Math.PI * β); return μ + σ * randStdNormal; } /// Picks a random element from a collection. public static T Pick(this IRobustRandom random, IReadOnlyList list) { var index = random.Next(list.Count); return list[index]; } /// Picks a random element from a collection. public static ref T Pick(this IRobustRandom random, ValueList list) { var index = random.Next(list.Count); return ref list[index]; } /// Picks a random element from a collection. public static ref T Pick(this System.Random random, ValueList list) { var index = random.Next(list.Count); return ref list[index]; } /// Picks a random element from a collection. /// /// This is O(n). /// public static T Pick(this IRobustRandom random, IReadOnlyCollection collection) { var index = random.Next(collection.Count); var i = 0; foreach (var t in collection) { if (i++ == index) { return t; } } throw new UnreachableException("This should be unreachable!"); } /// /// Picks a random element from a list, removes it from list and returns it. /// This is O(n) as it preserves the order of other items in the list. /// public static T PickAndTake(this IRobustRandom random, IList list) { var index = random.Next(list.Count); var element = list[index]; list.RemoveAt(index); return element; } /// /// Picks a random element from a set and returns it. /// This is O(n) as it has to iterate the collection until the target index. /// [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static T Pick(this System.Random random, ICollection collection) { var index = random.Next(collection.Count); var i = 0; foreach (var t in collection) { if (i++ == index) { return t; } } throw new UnreachableException("This should be unreachable!"); } /// /// Picks a random from a collection then removes it and returns it. /// This is O(n) as it has to iterate the collection until the target index. /// [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static T PickAndTake(this System.Random random, ICollection set) { var tile = Pick(random, set); set.Remove(tile); return tile; } /// /// Generate a random number from a normal (gaussian) distribution. /// /// The random object to generate the number from. /// The average or "center" of the normal distribution. /// The standard deviation of the normal distribution. [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static double NextGaussian(this System.Random random, double μ = 0, double σ = 1) { // https://stackoverflow.com/a/218600 var α = random.NextDouble(); var β = random.NextDouble(); var randStdNormal = Math.Sqrt(-2.0 * Math.Log(α)) * Math.Sin(2.0 * Math.PI * β); return μ + σ * randStdNormal; } [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static Angle NextAngle(this System.Random random) => NextFloat(random) * MathF.Tau; [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static Angle NextAngle(this System.Random random, Angle minAngle, Angle maxAngle) { DebugTools.Assert(minAngle < maxAngle); return minAngle + (maxAngle - minAngle) * random.NextDouble(); } [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static Vector2 NextPolarVector2(this System.Random random, float minMagnitude, float maxMagnitude) => random.NextAngle().RotateVec(new Vector2(random.NextFloat(minMagnitude, maxMagnitude), 0)); [Obsolete("Exists as a method directly on IRobustRandom.")] public static float NextFloat(this IRobustRandom random) { // This is pretty much the CoreFX implementation. // So credits to that. // Except using float instead of double. return random.Next() * 4.6566128752458E-10f; } [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static float NextFloat(this System.Random random) { return random.Next() * 4.6566128752458E-10f; } [Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")] public static float NextFloat(this System.Random random, float minValue, float maxValue) => random.NextFloat() * (maxValue - minValue) + minValue; /// /// Have a certain chance to return a boolean. /// /// The random instance to run on. /// The chance to pass, from 0 to 1. public static bool Prob(this IRobustRandom random, float chance) { DebugTools.Assert(chance <= 1 && chance >= 0, $"Chance must be in the range 0-1. It was {chance}."); return random.NextDouble() < chance; } /// /// Get set amount of random items from a collection. /// If is false and /// is smaller then - returns shuffled clone. /// If is empty, and/or is 0, returns empty. /// /// Instance of random to invoke upon. /// Collection from which items should be picked. /// Number of random items to be picked. /// If true, items are allowed to be picked more than once. public static T[] GetItems(this IRobustRandom random, IList source, int count, bool allowDuplicates = true) { if (source.Count == 0 || count <= 0) return Array.Empty(); if (allowDuplicates == false && count >= source.Count) { var arr = source.ToArray(); // Explicit type cast to IList to avoid calling the Span overload. // We have some tests that rely on mocking of this call, and Moq doesn't support Span atm. // https://github.com/space-wizards/RobustToolbox/issues/6329 random.Shuffle((IList)arr); return arr; } var sourceCount = source.Count; var result = new T[count]; if (allowDuplicates) { for (var i = 0; i < count; i++) { result[i] = source[random.Next(sourceCount)]; } return result; } var indices = sourceCount <= 1024 ? stackalloc int[sourceCount] : new int[sourceCount]; for (var i = 0; i < sourceCount; i++) { indices[i] = i; } for (var i = 0; i < count; i++) { var j = random.Next(sourceCount - i); result[i] = source[indices[j]]; indices[j] = indices[sourceCount - i - 1]; } return result; } /// public static T[] GetItems(this IRobustRandom random, ValueList source, int count, bool allowDuplicates = true) { return GetItems(random, source.Span, count, allowDuplicates); } /// public static T[] GetItems(this IRobustRandom random, T[] source, int count, bool allowDuplicates = true) { return GetItems(random, source.AsSpan(), count, allowDuplicates); } /// public static T[] GetItems(this IRobustRandom random, Span source, int count, bool allowDuplicates = true) { if (source.Length == 0 || count <= 0) return Array.Empty(); if (allowDuplicates == false && count >= source.Length) { var arr = source.ToArray(); // Explicit type cast to IList to avoid calling the Span overload. // We have some tests that rely on mocking of this call, and Moq doesn't support Span atm. // https://github.com/space-wizards/RobustToolbox/issues/6329 random.Shuffle((IList)arr); return arr; } var sourceCount = source.Length; var result = new T[count]; if (allowDuplicates) { // TODO RANDOM consider just using System.Random.GetItems() // However, the different implementations might mean that lists & arrays shuffled using the same seed // generate different results, which might be undesirable? for (var i = 0; i < count; i++) { result[i] = source[random.Next(sourceCount)]; } return result; } var indices = sourceCount <= 1024 ? stackalloc int[sourceCount] : new int[sourceCount]; for (var i = 0; i < sourceCount; i++) { indices[i] = i; } for (var i = 0; i < count; i++) { var j = random.Next(sourceCount - i); result[i] = source[indices[j]]; indices[j] = indices[sourceCount - i - 1]; } return result; } }