it-roy-ru.com

Лучший способ рандомизировать массив с .NET

Каков наилучший способ рандомизировать массив строк с помощью .NET? Мой массив содержит около 500 строк, и я хотел бы создать новую Array с теми же строками, но в случайном порядке.

Пожалуйста, включите пример C # в ваш ответ.

118
Mats

Если вы используете .NET 3.5, вы можете использовать следующую крутость IEnumerable (VB.NET, а не C #, но идея должна быть ясной ...):

Random rnd=new Random();
string[] MyRandomArray = MyArray.OrderBy(x => rnd.Next()).ToArray();    

Правка: ОК, и вот соответствующий код VB.NET:

Dim rnd As New System.Random
Dim MyRandomArray = MyArray.OrderBy(Function() rnd.Next()).ToArray()

Второе редактирование в ответ на замечания о том, что System.Random «не является потокобезопасным» и «подходит только для игрушечных приложений» из-за возврата последовательности, основанной на времени: как используется в моем примере, функция Random () является полностью поточно-ориентированной, если только вы позволяете повторно ввести процедуру, в которой вы рандомизируете массив, в этом случае вам все равно понадобится что-то вроде lock (MyRandomArray), чтобы не повредить ваши данные, что также защитит rnd

Также следует понимать, что System.Random как источник энтропии не очень силен. Как отмечено в документации MSDN , вы должны использовать что-то, полученное из System.Security.Cryptography.RandomNumberGenerator, если вы делаете что-то связанное с безопасностью. Например:

using System.Security.Cryptography;

...

RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();

...

static int GetNextInt32(RNGCryptoServiceProvider rnd)
    {
        byte[] randomInt = new byte[4];
        rnd.GetBytes(randomInt);
        return Convert.ToInt32(randomInt[0]);
    }
146
mdb

Следующая реализация использует алгоритм Фишера-Йейтса . Он выполняется за O(n) время и перетасовывается на месте, поэтому он более эффективен, чем метод «сортировки по случайному принципу», хотя в нем больше строк кода. Смотрите здесь для некоторых сравнительных измерений производительности. Я использовал System.Random, что хорошо для некриптографических целей. *

static class RandomExtensions
{
    public static void Shuffle<T> (this Random rng, T[] array)
    {
        int n = array.Length;
        while (n > 1) 
        {
            int k = rng.Next(n--);
            T temp = array[n];
            array[n] = array[k];
            array[k] = temp;
        }
    }
}

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

var array = new int[] {1, 2, 3, 4};
new Random().Shuffle(array);

* Для более длинных массивов, чтобы сделать (чрезвычайно большое) число перестановок одинаково вероятным, было бы необходимо запустить генератор псевдослучайных чисел (PRNG) через множество итераций для каждого обмена, чтобы произвести достаточно энтропии. Для массива из 500 элементов только очень малая часть из возможных 500! Перестановки можно будет получить с помощью PRNG. Тем не менее, алгоритм Фишера-Йейтса беспристрастен, и поэтому перемешивание будет таким же хорошим, как и используемый вами ГСЧ.

181
Matt Howells

Вы ищете алгоритм тасования, верно?

Ладно, есть два способа сделать это: умные, но люди, которые, кажется, всегда ошибаются, понимают: «И все-таки-неправильно», так что, может быть, «не такой уж умный»? путь, и тупой, как скалы, но кто заботится, потому что это работает путь.

Тупой путь

  • Создайте дубликат вашего первого массива, но пометить каждую строку случайным числом.
  • Сортировать дубликат массива по случайному числу.

Этот алгоритм работает хорошо, но убедитесь, что ваш генератор случайных чисел вряд ли пометит две строки одним и тем же номером. Из-за так называемого парадокса дня рождения , это происходит чаще, чем вы могли ожидать. Его временная сложность O (n log n).

Умный путь

Я опишу это как рекурсивный алгоритм:

Чтобы перетасовать массив размером n (индексы в диапазоне [0 ..n- 1]):

если n = 0
  • ничего не делать
если n> 0
  • (рекурсивный шаг) перетасовать первые n- 1 элементов массива
  • выберите случайный индекс x в диапазоне [0 ..n- 1]
  • поменяйте местами элемент с индексом n- 1 с элементом с индексом x

Итеративный эквивалент состоит в том, чтобы пройти итератор по массиву, меняя местами случайные элементы, но обратите внимание, что вы не можете поменять местами элемент после того, на который указывает итератор. Это очень распространенная ошибка, которая приводит к предвзятому перемешиванию.

Временная сложность O (n).

16
Pitarou

Этот алгоритм прост, но не эффективен, O (N2). Все алгоритмы "упорядочения по", как правило, O (N log N). Это, вероятно, не имеет значения ниже сотен тысяч элементов, но это было бы для больших списков.

var stringlist = ... // add your values to stringlist

var r = new Random();

var res = new List<string>(stringlist.Count);

while (stringlist.Count >0)
{
   var i = r.Next(stringlist.Count);
   res.Add(stringlist[i]);
   stringlist.RemoveAt(i);
}

Причина, почему это O (N2) является тонким: List.RemoveAt () является операцией O(N), если вы не удалите по порядку с конца.

8
Sklivvz

Вы также можете сделать метод расширения из Мэтта Хауэллса. Пример.

   namespace System
    {
        public static class MSSystemExtenstions
        {
            private static Random rng = new Random();
            public static void Shuffle<T>(this T[] array)
            {
                rng = new Random();
                int n = array.Length;
                while (n > 1)
                {
                    int k = rng.Next(n);
                    n--;
                    T temp = array[n];
                    array[n] = array[k];
                    array[k] = temp;
                }
            }
        }
    }

Тогда вы можете просто использовать его как: 

        string[] names = new string[] {
                "Aaron Moline1", 
                "Aaron Moline2", 
                "Aaron Moline3", 
                "Aaron Moline4", 
                "Aaron Moline5", 
                "Aaron Moline6", 
                "Aaron Moline7", 
                "Aaron Moline8", 
                "Aaron Moline9", 
            };
        names.Shuffle<string>();
4
Aaron

Рандомизация массива интенсивна, так как вам приходится перемещаться по куче строк. Почему бы просто не прочитать случайно из массива? В худшем случае вы могли бы даже создать класс-оболочку с помощью getNextString (). Если вам действительно нужно создать случайный массив, то вы можете сделать что-то вроде

for i = 0 -> i= array.length * 5
   swap two strings in random places

* 5 является произвольным. 

1
stimms

Просто подумав, вы можете сделать это:

public string[] Randomize(string[] input)
{
  List<string> inputList = input.ToList();
  string[] output = new string[input.Length];
  Random randomizer = new Random();
  int i = 0;

  while (inputList.Count > 0)
  {
    int index = r.Next(inputList.Count);
    output[i++] = inputList[index];
    inputList.RemoveAt(index);
  }

  return (output);
}
1
Tarsier

На этот пост уже довольно хорошо ответили - используйте быструю и непредвзятую реализацию Fisher-Yates в Durstenfeld. Были даже опубликованы некоторые реализации, хотя я отмечаю, что некоторые из них на самом деле неверны.

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

1
Greg Beech

Создайте массив случайных чисел с плавающей запятой или целых чисел одинаковой длины. Сортируйте этот массив и сделайте соответствующие замены в вашем целевом массиве.

Это дает действительно независимый вид.

0
Nick
Random r = new Random();
List<string> list = new List(originalArray);
List<string> randomStrings = new List();

while(list.Count > 0)
{
int i = r.Random(list.Count);
randomStrings.Add(list[i]);
list.RemoveAt(i);
}
0
nullDev

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

class Program
{
    static string[] words1 = new string[] { "brown", "jumped", "the", "fox", "quick" };

    static void Main()
    {
        var result = Shuffle(words1);
        foreach (var i in result)
        {
            Console.Write(i + " ");
        }
        Console.ReadKey();
    }

   static string[] Shuffle(string[] wordArray) {
        Random random = new Random();
        for (int i = wordArray.Length - 1; i > 0; i--)
        {
            int swapIndex = random.Next(i + 1);
            string temp = wordArray[i];
            wordArray[i] = wordArray[swapIndex];
            wordArray[swapIndex] = temp;
        }
        return wordArray;
    }         
}
0
usefulBee

Этот код перемешивает числа в массиве.

using System;

// ...
    static void Main(string[] args)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(numbers);

        for (int i = 0; i < numbers.Length; i++)
            Console.Write(numbers[i] + (i < numbers.Length - 1 ? ", " : null));
        Console.WriteLine();

        string[] words = { "this", "is", "a", "string", "of", "words" };
        Shuffle(words);

        for (int i = 0; i < words.Length; i++)
            Console.Write(words[i] + (i < words.Length - 1 ? ", " : null));
        Console.WriteLine();

        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write("Press any key to quit . . . ");
        Console.ReadKey(true);
    }

    static void Shuffle<T>(T[] array)
    {
        Random random = new Random();

        for (int i = 0; i < array.Length; i++)
        {
            T temporary = array[i];
            int intrandom = random.Next(array.Length);
            array[i] = array[intrandom];
            array[intrandom] = temporary;
        }
    }
0
Bilal

Вам не нужны сложные алгоритмы.

Всего одна простая строка:

Random random = new Random();
array.ToList().Sort((x, y) => random.Next(-1, 1)).ToArray();

Обратите внимание, что нам нужно сначала преобразовать Array в List, если вы не используете List во-первых.

Также учтите, что это не эффективно для очень больших массивов! В противном случае это чисто и просто.

0
bytecode77

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

public static class EnumerableExtensions
{
    static readonly RNGCryptoServiceProvider RngCryptoServiceProvider = new RNGCryptoServiceProvider();
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
    {
        var randomIntegerBuffer = new byte[4];
        Func<int> Rand = () =>
                             {
                                 RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);
                                 return BitConverter.ToInt32(randomIntegerBuffer, 0);
                             };
        return from item in enumerable
               let rec = new {item, rnd = Rand()}
               orderby rec.rnd
               select rec.item;
    }
}

Shuffle () является расширением для любого IEnumerable, поэтому получение, скажем, чисел от 0 до 1000 в случайном порядке в списке можно сделать с помощью

Enumerable.Range(0,1000).Shuffle().ToList()

Этот метод также не преподнесет никаких сюрпризов, когда дело доходит до сортировки, так как значение сортировки генерируется и запоминается ровно один раз для каждого элемента в последовательности.

0
jlarsson

Жако, твое решение - использовать нестандартный IComparer. Процедуры сортировки требуют, чтобы компаратор соответствовал нескольким требованиям для правильной работы. Первым среди них является последовательность. Если компаратор вызывается для одной и той же пары объектов, он всегда должен возвращать один и тот же результат. (сравнение также должно быть транзитивным).

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

Что касается решений, которые связывают случайное числовое значение с каждой записью, а затем сортируют по этому значению, это приводит к внутреннему смещению в выходных данных, поскольку в любое время, когда двум записям присваивается одно и то же числовое значение, случайность выходных данных будет скомпрометирована. (В «стабильной» процедуре сортировки, то, что будет первым во входных данных, будет первым в выходных данных. Array.Sort не является стабильным, но все же существует смещение, основанное на разделении, выполненном алгоритмом быстрой сортировки).

Вам нужно подумать о том, какой уровень случайности вам нужен. Если вы работаете на покерном сайте, где вам нужны криптографические уровни случайности для защиты от решительного злоумышленника, у вас очень разные требования к тому, кто просто хочет рандомизировать список воспроизведения песни.

Для перестановки списка песен нет проблем с использованием засеянного PRNG (например, System.Random). Для покерного сайта это даже не вариант, и вам нужно думать о проблеме намного сложнее, чем кто-либо собирается сделать для вас в stackoverflow. (использование криптографического ГСЧ - это только начало, вам нужно убедиться, что ваш алгоритм не вносит смещения, что у вас достаточно источников энтропии и что вы не выставляете какое-либо внутреннее состояние, которое может поставить под угрозу последующую случайность).

0
Andrew
        int[] numbers = {0,1,2,3,4,5,6,7,8,9};
        List<int> numList = new List<int>();
        numList.AddRange(numbers);

        Console.WriteLine("Original Order");
        for (int i = 0; i < numList.Count; i++)
        {
            Console.Write(String.Format("{0} ",numList[i]));
        }

        Random random = new Random();
        Console.WriteLine("\n\nRandom Order");
        for (int i = 0; i < numList.Capacity; i++)
        {
            int randomIndex = random.Next(numList.Count);
            Console.Write(String.Format("{0} ", numList[randomIndex]));
            numList.RemoveAt(randomIndex);
        }
        Console.ReadLine();
0
Nitesh Katare