Это мой предпочтительный метод случайного воспроизведения, когда желательно не изменять оригинал. Это вариант алгоритма «наизнанку» Фишера-Йейтса , который работает с любой перечисляемой последовательностью (длина source
не обязательно должна быть известна с самого начала).
public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
var list = new List<T>();
foreach (var item in source)
{
var i = r.Next(list.Count + 1);
if (i == list.Count)
{
list.Add(item);
}
else
{
var temp = list[i];
list[i] = item;
list.Add(temp);
}
}
return list;
}
Этот алгоритм также может быть реализован путем выделения диапазона от 0
до length - 1
и случайного исчерпания индексов путем замены произвольно выбранного индекса на последний индекс, пока все индексы не будут выбраны ровно один раз. Этот код выше выполняет то же самое, но без дополнительного выделения. Что довольно аккуратно.
Что касается класса Random
, то это генератор чисел общего назначения (и если бы я проводил лотерею, я бы подумал об использовании чего-то другого). По умолчанию также используется начальное значение времени. Небольшое облегчение проблемы состоит в том, чтобы заполнить класс Random
классом RNGCryptoServiceProvider
, или вы можете использовать RNGCryptoServiceProvider
в методе, подобном этому (см. Ниже), чтобы генерировать равномерно выбранные случайные значения с двойной плавающей запятой, но проводить лотерею в значительной степени требует понимания случайности и природы источника случайности.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
Точка генерации случайного двойного числа (исключительно между 0 и 1) заключается в использовании для масштабирования до целочисленного решения. Если вам нужно выбрать что-то из списка, основанного на случайном двойном x
, это всегда будет 0 <= x && x < 1
, просто.
return list[(int)(x * list.Count)];
Наслаждайтесь!