Рандомизировать список <T> - PullRequest
752 голосов
/ 07 ноября 2008

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

Ответы [ 18 ]

3 голосов
/ 19 сентября 2015

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

Наслаждайтесь!

2 голосов
/ 22 декабря 2013

Если вы не возражаете против использования двух 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);
1 голос
/ 16 июня 2014
 public Deck(IEnumerable<Card> initialCards) 
    {
    cards = new List<Card>(initialCards);
    public void Shuffle() 
     }
    {
        List<Card> NewCards = new List<Card>();
        while (cards.Count > 0) 
        {
            int CardToMove = random.Next(cards.Count);
            NewCards.Add(cards[CardToMove]);
            cards.RemoveAt(CardToMove);
        }
        cards = NewCards;
    }

public IEnumerable<string> GetCardNames() 

{
    string[] CardNames = new string[cards.Count];
    for (int i = 0; i < cards.Count; i++)
    CardNames[i] = cards[i].Name;
    return CardNames;
}

Deck deck1;
Deck deck2;
Random random = new Random();

public Form1() 
{

InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
 RedrawDeck(2);

}



 private void ResetDeck(int deckNumber) 
    {
    if (deckNumber == 1) 
{
      int numberOfCards = random.Next(1, 11);
      deck1 = new Deck(new Card[] { });
      for (int i = 0; i < numberOfCards; i++)
           deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
       deck1.Sort();
}


   else
    deck2 = new Deck();
 }

private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);

}

private void shuffle1_Click(object sender, EventArgs e) 
{
    deck1.Shuffle();
    RedrawDeck(1);

}

private void moveToDeck1_Click(object sender, EventArgs e) 
{

    if (listBox2.SelectedIndex >= 0)
    if (deck2.Count > 0) {
    deck1.Add(deck2.Deal(listBox2.SelectedIndex));

}

    RedrawDeck(1);
    RedrawDeck(2);

}
0 голосов
/ 28 марта 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());
    }
}
0 голосов
/ 08 сентября 2017

Простая модификация принятого ответа , которая возвращает новый список вместо того, чтобы работать на месте, и принимает более общий 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;
}
0 голосов
/ 11 апреля 2015

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

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

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

0 голосов
/ 07 ноября 2008

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

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

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times
0 голосов
/ 25 января 2013

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

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

`

...