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;
}
}