Произвести случайный выбор списка ‹T›

Как лучше всего рандомизировать порядок общего списка в C #? У меня есть конечный набор из 75 чисел в списке, которому я хотел бы назначить случайный порядок, чтобы нарисовать их для приложения типа лотереи.


person mirezus    schedule 07.11.2008    source источник
comment
Существует нерешенная проблема интеграции этой функции в .NET: github.com/dotnet/corefx/issues / 461   -  person Natan    schedule 07.03.2015
comment
Возможно, вас заинтересует этот пакет NuGet, который содержит методы расширения для перемешивание IList ‹T› и IEnumerable ‹T› с использованием алгоритма Фишера-Йейтса, упомянутого ниже   -  person ChaseMedallion    schedule 07.05.2016
comment
@ Natan fyi, они его убили   -  person Chris Marisic    schedule 06.06.2016
comment
Также имеется связанный Выберите N случайных элементов из списка ‹ T › и Перемешать с OrderBy vs. Fisher- Йетс обсуждения.   -  person Alexei Levenkov    schedule 05.02.2017
comment
Может у вас есть бесконечный набор из 75 чисел;)?   -  person tymtam    schedule 28.09.2017
comment
@Natan они закрыли вопрос, потому что кто-то работал над множеством проектов и разработал множество библиотек, и никогда не нуждался в таком методе, который меня бесил. Теперь нам нужно исследовать себя, искать лучшие реализации, тратить время на то, чтобы просто изобретать велосипед.   -  person Vitalii Isaenko    schedule 16.07.2019
comment
Я правильно понимаю? Ни одного действительного функционального ответа за 10 лет? Может быть, нам нужна еще одна награда для решения, которое учитывает количество необходимой энтропии, чтобы перемешать список с 75 числами $ log2 (75!) = 364 $ и как мы можем это получить. Даже криптографически безопасный ГСЧ с 256 битами энтропии потребуется повторно загрузить хотя бы один раз во время тасования Фишера-Ятса.   -  person Falco    schedule 01.08.2019
comment
И если обычный кодер не может решить эту проблему, все ли мы вечно играем в одни и те же 0,01% возможных пасьянсов?   -  person Falco    schedule 01.08.2019


Ответы (22)


Перемешайте любой (I)List с помощью метода расширения, основанного на перемешивании Фишера-Йейтса:

private static Random rng = new Random();  

public static void Shuffle<T>(this IList<T> list)  
{  
    int n = list.Count;  
    while (n > 1) {  
        n--;  
        int k = rng.Next(n + 1);  
        T value = list[k];  
        list[k] = list[n];  
        list[n] = value;  
    }  
}

Использование:

List<Product> products = GetProducts();
products.Shuffle();

В приведенном выше коде для выбора кандидатов на замену используется очень критикуемый метод System.Random. Это быстро, но не так случайно, как должно быть. Если вам нужно лучшее качество случайности в перемешивании, используйте генератор случайных чисел в System.Security.Cryptography следующим образом:

using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> list)
{
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    int n = list.Count;
    while (n > 1)
    {
        byte[] box = new byte[1];
        do provider.GetBytes(box);
        while (!(box[0] < n * (Byte.MaxValue / n)));
        int k = (box[0] % n);
        n--;
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

Доступно простое сравнение в этом блоге (WayBack Machine).

Изменить: с момента написания этого ответа пару лет назад многие люди комментировали или писали мне, чтобы указать на большой глупый недостаток в моем сравнении. Конечно, они правы. В System.Random нет ничего плохого, если он используется по назначению. В моем первом примере выше я создаю экземпляр переменной rng внутри метода Shuffle, который вызывает проблемы, если метод будет вызываться повторно. Ниже приведен фиксированный полный пример, основанный на действительно полезном комментарии, полученном сегодня от @weston здесь, на SO.

Program.cs:

using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleLottery
{
  class Program
  {
    private static void Main(string[] args)
    {
      var numbers = new List<int>(Enumerable.Range(1, 75));
      numbers.Shuffle();
      Console.WriteLine("The winning numbers are: {0}", string.Join(",  ", numbers.GetRange(0, 5)));
    }
  }

  public static class ThreadSafeRandom
  {
      [ThreadStatic] private static Random Local;

      public static Random ThisThreadsRandom
      {
          get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
      }
  }

  static class MyExtensions
  {
    public static void Shuffle<T>(this IList<T> list)
    {
      int n = list.Count;
      while (n > 1)
      {
        n--;
        int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
      }
    }
  }
}
person grenade    schedule 11.08.2009
comment
Отлично! Обожаю методы расширения: D - person CodeAndCats; 24.09.2009
comment
Обратите внимание, что производительность этого значительно ниже в LinkedLists по сравнению с ArrayLists. Обязательно отправляйте ему индексируемый массив производительности O (1). - person TheSoftwareJedi; 12.01.2010
comment
@Jedi, если бы это было серьезной проблемой, возможно, было бы полезно создать список вторичного хранилища или массив, произвести там перетасовку и заменить им содержимое списка. - person corsiKa; 21.03.2011
comment
Что, если list.Count имеет значение ›Byte.MaxValue? Если n = 1000, то 255/1000 = 0, поэтому цикл do будет бесконечным, поскольку box [0] ‹0 всегда ложно. - person AndrewS; 07.06.2011
comment
Хочу отметить, что сравнение некорректно. Проблема в использовании ‹code› new Random () ‹/code› в цикле, а не в случайности ‹code› Random ‹/code› Пояснение - person Sven; 29.09.2011
comment
Рекомендуется передать экземпляр Random в метод Shuffle, а не создавать его внутри, как если бы вы вызывали Shuffle много раз в быстрой последовательности (например, перемешивание большого количества коротких списков), все списки будут перемешаны в одном и том же способ (например, первый элемент всегда перемещается в позицию 3). - person Mark Heath; 08.02.2012
comment
В вашей сравнительной статье немного не хватает сути. System.Random не так плохо, как вы показываете. new Random() - это с использованием зависящего от времени начального значения по умолчанию, поэтому в вашем тесте вообще нет рандомизации. По истинной причине см. boallen.com/random-numbers.html, но опять же, это немного Зависит от ОС, поэтому System.Random может работать лучше или нет. - person Wernight; 11.09.2012
comment
Простое создание Random rng = new Random(); a static решит проблему в сравнительной публикации. Поскольку каждый последующий вызов будет следовать за предыдущими вызовами, последний случайный результат. - person weston; 28.11.2012
comment
Не забудьте удалить случайного поставщика после того, как закончите его использовать. - person Andrei Micu; 23.12.2012
comment
Создание статического значения Random - опасная идея, потому что теперь этот метод не является потокобезопасным. А как насчет точки AndrewS в безопасной версии? - person Mark Sowul; 08.05.2013
comment
Я буду честен @MarkSowul и скажу вам, что я мало знаю об опасностях статики или безопасности потоков. Съедут ли меня львы, заставят ли меня пони или с какими опасностями я столкнусь? Я также не знаю ответа или заявления, что понимаю вопрос, заданный AndrewS. - person grenade; 08.05.2013
comment
Если два разных потока используют версию с общим ГСЧ одновременно, ГСЧ может повредить его состояние, поскольку он не является потокобезопасным (stackoverflow.com/a/11109361/155892). - person Mark Sowul; 08.05.2013
comment
# 2, неясно, работает ли версия с генератором криптографии, потому что максимальный диапазон байта равен 255, поэтому любой список большего размера не будет правильно перемешиваться. - person Mark Sowul; 08.05.2013
comment
Я согласен с тем, что моя идея не была потокобезопасной, и, поскольку вы не понимали проблем безопасности потоков, я добавил ThreadSafeRandom класс на основе ответа Марка Соулса, надеюсь, это круто! - person weston; 10.05.2013
comment
Если бы вы использовали ThreadSafeRandom в ASP.NET, я полагаю, вы бы сгенерировали много-много экземпляров Local. Я бы даже не подумал, что они будут собирать мусор, пока AppDomain не сломается. - person Chris Marisic; 17.09.2014
comment
зачем делать provider.GetBytes (box); while (! (box [0] ‹n * (Byte.MaxValue / n))); int k = (коробка [0]% n); вместо do provider.GetBytes (box); while (поле [0] ›= n); int k = box [0]; - person roim; 09.10.2014
comment
Скажем, кто-то хочет предоставить копию перетасованного списка, сохранив оригинал нетронутым. Как бы это сделать? - person Will; 22.12.2014
comment
Несмотря на то, что существует новая и улучшенная поточно-ориентированная версия, у вас все равно будет та же проблема, если один поток вызывает Shuffle в тесном цикле. Я отредактировал исходный пример, чтобы люди, которые просто копируют и вставляют решение, не читая обсуждения, не столкнутся с проблемой жесткого цикла. - person Eric J.; 16.07.2015
comment
@Will: Сделайте копию перед перемешиванием и перемешайте эту копию. Для этого отлично подойдет метод расширения Linq .ToList (), но обратите внимание, если у вас есть список ссылочного типа, оба будут ссылаться на исходный объект, например. если у вас List<Person> a = LoadSomehow(); List<Person> b = a.ToList(); b.Shuffle<string>();, в двух списках будут одинаковые экземпляры Person, но в разном порядке. - person Eric J.; 16.07.2015
comment
Необходимо внимательно изучить количество бит энтропии в вашем ГПСЧ. Например, чтобы перетасовать колоду из 52 карт и получить все возможные перетасованные колоды и сделать это с равной вероятностью, вам понадобится ГПСЧ с как минимум 226-битным начальным значением! Пожалуйста, не делайте этого. Не используйте методы, описанные в этом ответе, для перетасовки вещей, когда требуется равная вероятность всех возможных результатов, если только вы не выполните надлежащие компьютерные науки, чтобы убедиться, что это работает правильно. - person ErikE; 14.10.2015
comment
Ваш пример RNGCryptoServiceProvider работал для меня бесконечно - person MickyD; 19.05.2016
comment
Я бы внес в это небольшое изменение, а именно, чтобы он копировал входящий список, перетасовывал этот новый список и возвращал его. Тогда вы можете делать такие вещи, как var shuffledList = originalList.Shuffle(); - person Dagrooms; 31.05.2016
comment
К сожалению, я не могу использовать поточно-ориентированную версию, так как я использую VB.NET и нет ни unchecked, ни подходящей альтернативы. - person Marco Sulla; 24.06.2016
comment
В чем смысл условия цикла do while? Почему бы просто не взять байт и затем выполнить% list.Count? - person Phoenix; 06.12.2016
comment
Чтобы достичь хорошей энтропии с лучшей производительностью, без глупого while-цикла или ограничения в 255 элементов, вы можете предварительно рассчитать количество случайных байтов, необходимых для выполнения алгоритма до завершения (sizeof(int) * (list.Count - 1) или long при работе с необработанными массивами) и заполнить байтовый массив одним вызовом GetBytes(). Затем оберните этот массив байтов в MemoryStream с помощью конструктора упаковки (практически бесплатно) и прочтите из него случайные целые числа с помощью BinaryReader. Не забывайте утилизировать одноразовые экземпляры! (Вы можете поддерживать более крупные списки, создав кольцевой буфер.) - person Xharlie; 04.01.2017
comment
зачем вам нужен случайный экземпляр, чтобы быть потокобезопасным? насколько я вижу, ваш код выполняет все вычисления в 1 потоке - person Daniel Perez; 13.12.2017
comment
Я думаю, что ваша реализация RNGCryptoServiceProvider создаст неопределенную случайность, когда количество списков превышает 255. У него также есть тенденция к бесконечному циклу по количеству списков. Доработка ниже: int RandomInteger () {cryptoServiceProvider.GetBytes (randomIntegerBuffer); вернуть BitConverter.ToInt32 (randomIntegerBuffer, 0); } var result = from item in source let record = new {Item = item, RandomKey = RandomInteger ()} orderby record.RandomKey select record.Item; - person William; 15.01.2019
comment
Мне пришлось изменить счетчик массива, поскольку он вырезал отсутствующий индекс массива 0 int listSize = list.Count; while (listSize ›0) // в то время как список больше 0, конечно, я новичок - так что я тоже могу делать это странно: P - person StackBuddy; 15.03.2019
comment
@William Я столкнулся именно с этой проблемой, просто попытка перетасовать 1000 элементов приводит к бесконечному циклу. НЕ ИСПОЛЬЗУЙТЕ первую версию метода в ответе. - person Vitalii Isaenko; 16.07.2019
comment
Мне просто интересно. Действительно ли необходимо сначала вычесть 1 из n, а затем прибавить его обратно? Кроме того, операторы посткремента создают дополнительный экземпляр, поэтому с точки зрения производительности лучше использовать здесь оператор прекремента. При этом я бы изменил две строки на следующие: первая int k = ThreadSafeRandom.ThisThreadsRandom.Next(n); вторая --n. Но в любом случае я благодарен вам за предоставленный алгоритм. - person qqqqqqq; 07.03.2020
comment
@Xharlie Я знаю, что это старый пост, но меня интересует ваша предлагаемая реализация для использования случайного байтового массива ... у вас есть образец, из которого я мог бы поучиться? Спасибо. - person Chris Keller; 09.05.2021

Если нам нужно только перемешать элементы в полностью случайном порядке (просто чтобы смешать элементы в списке), я предпочитаю этот простой, но эффективный код, который упорядочивает элементы по руководству ...

var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();

Как отмечали люди в комментариях, не гарантируется, что GUID будут случайными, поэтому вместо этого мы должны использовать настоящий генератор случайных чисел:

private static Random rng = new Random();
...
var shuffledcards = cards.OrderBy(a => rng.Next()).ToList();
person user453230    schedule 23.11.2010
comment
Здесь используется метод Linq для изменения порядка в списке. NewGuid создает случайный Guid (16-байтовое значение). OrderBy использует предоставленную функцию для назначения сопоставимого объекта каждой карточке, а затем возвращает новую коллекцию, заказанную руководителями. Поскольку Guid очень длинные, вероятность столкновения (один и тот же Guid для двух разных объектов) очень мала. - person Christopher Stevenson; 28.03.2013
comment
Как вернуть это в список? list = (List ‹string›) list.OrderBy () является недопустимым приведением - person ; 14.04.2013
comment
@Mark list.OrderyBy (...). ToList (); - person mirezus; 22.04.2013
comment
Идентификаторы GUID должны быть уникальными, а не случайными. Часть из них основывается на машинах, другая - на части рабочего времени, и лишь небольшая часть является случайной. blogs.msdn.com/b/oldnewthing/archive/ 27 июня 2008 г. / 8659071.aspx - person Despertar; 05.05.2013
comment
Это красивое элегантное решение. Если вы хотите, чтобы что-то другое, кроме руководства, генерировало случайность, просто закажите что-нибудь еще. Например: var shuffledcards = cards.OrderBy(a => rng.Next()); compilr.com/grenade/sandbox/Program.cs. - person grenade; 27.05.2013
comment
Обратите внимание, что только определенные типы GUID являются машинными или временными. GUID, созданные таким образом (Guid.NewGuid ()), не являются ни машинными, ни временными (то есть последовательными GUID). Это хороший способ перемешать список. - person Doug; 12.06.2013
comment
Еще одна вещь, на которую следует обратить внимание, - это то, что лямбда может вызываться несколько раз, и в этом случае она вернет разные значения для одного и того же элемента. Это может замедлить сортировку. Однако я не знаю, относится ли это к стандартной реализации OrderBy. В любом случае я бы рекомендовал использовать тасование Фишера-Йейтса. - person Herman; 24.07.2013
comment
Пожалуйста, нет. Это не правильно. случайное упорядочение - это НЕ перемешивание: вы вводите смещение и, что еще хуже, рискуете зайти в бесконечные циклы - person Vito De Tullio; 16.08.2013
comment
@VitoDeTullio: Вы неправильно помните. Вы рискуете получить бесконечные циклы, если предоставите случайную функцию сравнения; функция сравнения необходима для получения согласованного общего порядка. Случайный ключ вполне подойдет. Это предложение неверно, потому что guids не обязательно будут случайными, а не потому, что метод сортировки по случайному ключу неправильный. - person Eric Lippert; 14.09.2013
comment
@Doug: NewGuid гарантирует только то, что дает вам уникальный GUID. Это не дает никаких гарантий относительно случайности. Если вы используете GUID не для создания уникального значения, вы делаете это неправильно. - person Eric Lippert; 14.09.2013
comment
@Eric - Вы правы, это, вероятно, не самый случайный способ перемешать список. Для заинтересованных: stackoverflow.com/questions/2621563/ - person Doug; 13.12.2013
comment
+1 Мне это непонятно, но это простое и единственное решение, которое сработало, спасибо! - person Ian Campbell; 30.05.2014
comment
Он выполняет свою работу элегантно. Многие люди просто хотят перемешать свои списки и не заботятся о том, насколько они случайны. Они предпочитают этот метод любому другому, более академическому методу. Я знаю, что. Спасибо! - person Rob Vermeulen; 22.06.2016
comment
Спасибо за этот ответ, иногда вам просто нужен простой способ немного перетасовать вещи, и, похоже, это все. - person Yann; 03.08.2016
comment
Есть ли гарантия, что ключевая функция вызывается только один раз для каждого элемента? (Я не нашел упоминания об этом в документации.) Если так, то это довольно элегантно. В противном случае этот код действительно рискует бесконечными циклами (или увеличением времени выполнения) и другими трудно предсказуемыми несоответствиями. - person Roland Illig; 05.08.2016
comment
@RolandIllig - Как это может привести к бесконечной петле? - person Enigmativity; 16.06.2017
comment
@RolandIllig (1) OrderBy(x => x.Id) (2) Orderby(x => 5) (3) OrderBy(x => rng.Next()) Ничто из этого не создает бесконечного цикла. значение, выбранное в OrderBy, не влияет на обработку элементов. Каждый элемент в списке получит свое значение ровно один раз. Обратите внимание, что вы были бы правы, если, например, OrderBy был пузырьковой сортировкой, при которой значения не кэшировались; но здесь дело обстоит не так. - person Flater; 28.05.2018
comment
@Flater Значение каждого элемента в списке будет извлечено ровно один раз - кто спрашивает? - person Roland Illig; 28.05.2018
comment
@RolandIllig Enumerable.Range(0,10).OrderBy(x => GetRandomNumberAndLogMessageToConsole()); Я полагаю, это имя достаточно наглядное. - person Flater; 28.05.2018
comment
@RolandIllig: Также на pastebin для вашего удобства. - person Flater; 28.05.2018
comment
Это отличный ответ, но я хочу добавить предупреждение людям, использующим его: это плохая идея, если у вас действительно большие списки. Обозначение O (n) для .OrderBy (RandomFunc) - это n * log (n), а не n. Для 1-1000 записей это не имеет большого значения. Для миллиона записей это будет примерно в 100 раз медленнее; для миллиарда записей это будет примерно в 1000 раз медленнее. - person Kevin; 14.06.2018
comment
Такая случайность привела к ошибке в моем приложении, на поиск которой потребовалось 4 дня (списки всегда были не случайными). Просто не делай этого. - person mafu; 26.06.2020
comment
Это простое решение для не криптографических целей. - person Justin; 15.07.2020
comment
Это НЕ решение заданного вопроса. Вопрос в том, как СЛУЧАЙНО ИЗМЕНИТЬ список. Этот ответ не случаен. Это необъективно. Конечно, он может перемешать список элементов, но не случайным образом. Случайный означает, что все уникальные комбинации могут возникать с равномерным распределением. - person Garrison Becker; 11.08.2020
comment
Фантастически, как это продолжает развиваться. Если вы посмотрите на исходный код в ядре dotnet, Guid.NewGuid() делает, как люди сказали, рисование из вызова взаимодействия с Windows ... если вы работаете в Windows. Однако все остальные ОС используют RNGCryptoServiceProvider , который, скорее всего, действительно является производным от случайных байтов. - person Jeff Putz; 18.10.2020
comment
Некоторая контрольная сумма Guid.NewGuid () может сделать это более случайным. - person jahu; 14.05.2021
comment
@GarrisonBecker - Для всех намерений и целей это случайно. См. stackoverflow.com/questions/67888049/bug-in- nets-random-class, демонстрирующий незначительный эффект использования Random. Добавьте настоящий ГПСЧ, и все в порядке. - person Enigmativity; 12.07.2021

Меня немного удивили все неуклюжие версии этого простого алгоритма. Фишер-Йейтс (или перетасовка Кнута) немного сложен, но очень компактен. Почему это сложно? Потому что вам нужно обратить внимание на то, возвращает ли ваш генератор случайных чисел r(a,b) значение, где b является включительным или исключающим. Я также отредактировал описание в Википедии, чтобы люди не слепо следуйте псевдокоду и создавайте трудно обнаруживаемые ошибки. Для .Net Random.Next(a,b) возвращает число без b, поэтому, без лишних слов, вот как это можно реализовать в C # /. Net:

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for(var i=list.Count; i > 0; i--)
        list.Swap(0, rnd.Next(0, i));
}

public static void Swap<T>(this IList<T> list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

Попробуйте этот код.

person Shital Shah    schedule 26.03.2014
comment
Этот код не работает должным образом. Последний номер всегда 0 или list.Count-1. - person Oneiros; 03.01.2020
comment
@ShitalShah Текущий код в вашем ответе не дает правильных результатов, потому что это неправильная перетасовка Фишера-Ятса. Это должно быть исправлено, как и код в ссылке. - person Trisibo; 13.07.2020
comment
Этот код не работает. Если вы использовали список строк для трех букв, A, B и C, CBA и BCA буквально никогда не использовались бы при использовании этой функции из-за этой строки: list.Swap(0, rnd.Next(0, i)); переключение на следующие исправляет это и делает его работающим, не -смещенная псевдослучайная функция: list.Swap(i-1, rnd.Next(0, i)); - person Garrison Becker; 10.08.2020

Метод расширения для IEnumerable:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}
person Denis    schedule 11.08.2010
comment
У этого алгоритма есть две существенные проблемы: - OrderBy использует вариант QuickSort для сортировки элементов по их (предположительно случайным) ключам. Производительность QuickSort составляет O (N log N); Напротив, тасование Фишера-Йетса - O (N). Для коллекции из 75 элементов это может не иметь большого значения, но разница станет заметной для больших коллекций. - person John Beyer; 26.06.2013
comment
... - Random.Next() может давать достаточно псевдослучайное распределение значений, но не гарантирует, что значения будут уникальными. Вероятность дублирования ключей растет (нелинейно) с N до тех пор, пока не достигнет достоверности, когда N достигнет 2 ^ 32 + 1. OrderBy QuickSort - это стабильная сортировка; таким образом, если нескольким элементам будет присвоено одно и то же значение псевдослучайного индекса, то их порядок в выходной последовательности будет таким же, как и во входной последовательности; таким образом, в тасование вносится смещение. - person John Beyer; 26.06.2013
comment
@JohnBeyer: Есть гораздо более серьезные проблемы, чем этот источник предвзятости. В Random есть только четыре миллиарда возможных начальных чисел, что намного меньше, чем количество возможных перетасовок для набора среднего размера. Может быть сгенерирована лишь крошечная часть возможных тасовок. Это смещение затмевает смещение из-за случайных столкновений. - person Eric Lippert; 14.09.2013
comment
Другая проблема с Random заключается в том, что когда два (или более) экземпляра Random создаются вскоре один за другим, они могут иметь одно и то же начальное число (начальное значение берется из системных часов, и разрешение часов может быть слишком большим, чтобы зарегистрировать изменение). - person jahu; 14.05.2021

Идея состоит в том, чтобы получить анонимный объект с элементом и случайным порядком, а затем переупорядочить элементы в этом порядке и вернуть значение:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()
person Andrey Kucher    schedule 31.07.2018

ИЗМЕНИТЬ RemoveAt - слабое место в моей предыдущей версии. Это решение преодолевает это.

public static IEnumerable<T> Shuffle<T>(
        this IEnumerable<T> source,
        Random generator = null)
{
    if (generator == null)
    {
        generator = new Random();
    }

    var elements = source.ToArray();
    for (var i = elements.Length - 1; i >= 0; i--)
    {
        var swapIndex = generator.Next(i + 1);
        yield return elements[swapIndex];
        elements[swapIndex] = elements[i];
    }
}

Обратите внимание на необязательный Random generator, если реализация базовой структуры Random не является потокобезопасной или криптографически достаточно сильной для ваших нужд, вы можете внедрить свою реализацию в операцию.

В этом ответе можно найти подходящую реализацию для потокобезопасной криптографически стойкой Random реализации.


Вот идея, как расширить IList (надеюсь) эффективным способом.

public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
    var choices = Enumerable.Range(0, list.Count).ToList();
    var rng = new Random();
    for(int n = choices.Count; n > 1; n--)
    {
        int k = rng.Next(n);
        yield return list[choices[k]];
        choices.RemoveAt(k);
    }

    yield return list[choices[0]];
}

person Jodrell    schedule 27.10.2011

Это мой предпочтительный метод перемешивания, когда желательно не изменять оригинал. Это вариант Фишера – Йейтса "внутри- out "алгоритм, который работает с любой перечислимой последовательностью (длину 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);

Смысл генерации случайного числа double (исключительно от 0 до 1) заключается в масштабировании до целочисленного решения. Если вам нужно выбрать что-то из списка, основанного на случайном двойном x, который всегда будет 0 <= x && x < 1, это просто.

return list[(int)(x * list.Count)];

Наслаждаться!

person John Leidegren    schedule 19.09.2015

Если вы не против использовать два Lists, то это, вероятно, самый простой способ сделать это, но, вероятно, не самый эффективный или непредсказуемый:

List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();

foreach (int xInt in xList)
    deck.Insert(random.Next(0, deck.Count + 1), xInt);
person Xelights    schedule 22.12.2013

Вы можете добиться этого, используя этот простой метод расширения

public static class IEnumerableExtensions
{

    public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
    {
        Random r = new Random();

        return target.OrderBy(x=>(r.Next()));
    }        
}

и вы можете использовать его, выполнив следующие

// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc

List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };

foreach (string s in myList.Randomize())
{
    Console.WriteLine(s);
}
person Shehab Fawzy    schedule 24.08.2014

Просто хотел предложить вариант с использованием IComparer<T> и List.Sort():

public class RandomIntComparer : IComparer<int>
{
    private readonly Random _random = new Random();
    
    public int Compare(int x, int y)
    {
        return _random.Next(-1, 2);
    }
}

Использование:

list.Sort(new RandomIntComparer());
person VBorisoff    schedule 08.09.2020

Если у вас есть фиксированное число (75), вы можете создать массив из 75 элементов, а затем пронумеровать свой список, перемещая элементы в рандомизированные позиции в массиве. Вы можете сгенерировать отображение номера списка в индекс массива, используя Fisher-Yates перемешать.

person dmo    schedule 07.11.2008

Обычно я использую:

var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
    var index = rnd.Next (0, list.Count);
    randomizedList.Add (list [index]);
    list.RemoveAt (index);
}
person albertein    schedule 07.11.2008

Я нашел интересное решение в сети.

Предоставлено: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/

var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();

person Ashokan Sivapragasam    schedule 24.05.2020

Простая модификация принятого ответа, которая возвращает новый список вместо работы на месте и принимает более общий IEnumerable<T> столько же другие методы Linq делают.

private static Random rng = new Random();

/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
    var source = list.ToList();
    int n = source.Count;
    var shuffled = new List<T>(n);
    shuffled.AddRange(source);
    while (n > 1) {
        n--;
        int k = rng.Next(n + 1);
        T value = shuffled[k];
        shuffled[k] = shuffled[n];
        shuffled[n] = value;
    }
    return shuffled;
}
person Extragorey    schedule 08.09.2017

Вот эффективный Shuffler, который возвращает байтовый массив перемешанных значений. Он никогда не перетасовывает больше, чем нужно. Его можно перезапустить с того места, где он был ранее остановлен. Моя фактическая реализация (не показана) - это компонент MEF, который позволяет использовать указанную пользователем замену.

    public byte[] Shuffle(byte[] array, int start, int count)
    {
        int n = array.Length - start;
        byte[] shuffled = new byte[count];
        for(int i = 0; i < count; i++, start++)
        {
            int k = UniformRandomGenerator.Next(n--) + start;
            shuffled[i] = array[k];
            array[k] = array[start];
            array[start] = shuffled[i];
        }
        return shuffled;
    }

`

person BSalita    schedule 24.01.2013

Вот поточно-ориентированный способ сделать это:

public static class EnumerableExtension
{
    private static Random globalRng = new Random();

    [ThreadStatic]
    private static Random _rng;

    private static Random rng 
    {
        get
        {
            if (_rng == null)
            {
                int seed;
                lock (globalRng)
                {
                    seed = globalRng.Next();
                }
                _rng = new Random(seed);
             }
             return _rng;
         }
    }

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
    {
        return items.OrderBy (i => rng.Next());
    }
}
person Christopher Stevenson    schedule 28.03.2013

Вот реализация тасования Фишера-Йейтса, которая позволяет указать количество возвращаемых элементов; следовательно, нет необходимости сначала сортировать всю коллекцию, прежде чем взять желаемое количество элементов.

Последовательность замены элементов обратная по умолчанию; и переходит от первого элемента к последнему, так что получение подмножества коллекции дает ту же (частичную) последовательность, что и перетасовка всей коллекции:

collection.TakeRandom(5).SequenceEqual(collection.Shuffle().Take(5)); // true

Этот алгоритм основан на (современной) версии Дарстенфельда Fisher-Yates shuffle в Википедии.

public static IList<T> TakeRandom<T>(this IEnumerable<T> collection, int count, Random random) => shuffle(collection, count, random);
public static IList<T> Shuffle<T>(this IEnumerable<T> collection, Random random) => shuffle(collection, null, random);
private static IList<T> shuffle<T>(IEnumerable<T> collection, int? take, Random random)
{
    var a = collection.ToArray();
    var n = a.Length;
    if (take <= 0 || take > n) throw new ArgumentException("Invalid number of elements to return.");
    var end = take ?? n;
    for (int i = 0; i < end; i++)
    {
        var j = random.Next(i, n);
        (a[i], a[j]) = (a[j], a[i]);
    }

    if (take.HasValue) return new ArraySegment<T>(a, 0, take.Value);
    return a;
}
person JohnC    schedule 25.07.2020

Ваш вопрос в том, как рандомизировать список. Это означает:

  1. Должны быть возможны все уникальные комбинации.
  2. Все уникальные комбинации должны происходить с одним и тем же распределением (AKA не является необъективным).

Большое количество ответов, опубликованных на этот вопрос, НЕ удовлетворяют двум вышеперечисленным требованиям к случайности.

Вот компактная, несмещенная псевдослучайная функция, соответствующая методу тасования Фишера-Йейтса.

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for (var i = list.Count-1; i > 0; i--)
    {
        var randomIndex = rnd.Next(i + 1); //maxValue (i + 1) is EXCLUSIVE
        list.Swap(i, randomIndex); 
    }
}

public static void Swap<T>(this IList<T> list, int indexA, int indexB)
{
   var temp = list[indexA];
   list[indexA] = list[indexB];
   list[indexB] = temp;
}
person Garrison Becker    schedule 10.08.2020

Можно использовать метод расширения Shuffle из пакета morelinq, он работает на IEnumerables

установочный пакет morelinq

using MoreLinq;
...    
var randomized = list.Shuffle();
person Adrian Rus    schedule 09.10.2020

private List<GameObject> ShuffleList(List<GameObject> ActualList) {


    List<GameObject> newList = ActualList;
    List<GameObject> outList = new List<GameObject>();

    int count = newList.Count;

    while (newList.Count > 0) {

        int rando = Random.Range(0, newList.Count);

        outList.Add(newList[rando]);

        newList.RemoveAt(rando);

     

    }

    return (outList);

}

использование :

List<GameObject> GetShuffle = ShuffleList(ActualList);
person Kannan K Mannadiar    schedule 06.11.2020

Старый пост точно, но я просто использую GUID.

Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();

Идентификатор GUID всегда уникален, и поскольку он восстанавливается каждый раз, когда результат изменяется каждый раз.

person DavidMc    schedule 11.04.2015
comment
Этот ответ уже был дан, и, что еще хуже, он рассчитан на уникальность, а не на случайность. - person Alex Angas; 05.01.2016

Очень простой подход к этой проблеме - использовать произвольное количество элементов в списке.

В псевдокоде это будет выглядеть так:

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times
person Aleris    schedule 07.11.2008
comment
Одна из проблем этого подхода - знать, когда остановиться. Он также имеет тенденцию преувеличивать любые смещения в генераторе псевдослучайных чисел. - person Mark Bessey; 07.11.2008
comment
да. Очень неэффективно. Нет причин использовать подобный подход, когда существуют более быстрые и более эффективные подходы, которые так же просты. - person PeterAllenWebb; 08.11.2008
comment
не очень эффективно или эффективно ... Запуск его N раз, скорее всего, оставит многие элементы в исходном положении. - person NSjonas; 08.12.2012