Найкращий спосіб рандомізувати масив за допомогою .NET


141

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

Будь ласка, включіть у свою відповідь приклад C #.


1
Ось дивне, але просте рішення для цього - stackoverflow.com/a/4262134/1298685 .
Іван Кемпбелл

1
Використовуючи пакет MedallionRandom NuGet, це просто myArray.Shuffled().ToArray()(або myArray.Shuffle()якщо ви бажаєте
відключити

Відповіді:


171

Якщо ви перебуваєте на .NET 3.5, ви можете використовувати таку численні прохолодність IEsumerable (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]);
    }

дві примітки: 1) System.Random не є безпечним для потоків (вас попереджали) та 2) System.Random, що базується на часі, тому, якщо ви використовуєте цей код у сильно сумісній системі, можливі два запити отримати те саме значення (тобто у веб-
сайтах

2
Просто для уточнення вище, System.Random буде насіння себе , використовуючи поточний час, так що два примірника , створювані одночасно буде генерувати один і той же «випадковий» sequence..System.Random слід використовувати тільки в іграшкових додатках
therealhoff

8
Також цей алгоритм є O (n log n) і зміщений алгоритмом Qsort. Дивіться мою відповідь щодо об'єктивного рішення O (n).
Метт Хоуеллз

9
Якщо не OrderByкешується внутрішніми ключами сортування, це також має проблему порушення перехідної властивості впорядкованих порівнянь. Якщо колись буде перевірка режиму налагодження, яка OrderByдала б правильні результати, то теоретично це може призвести до виключення.
Сем Харвелл


205

Наступна реалізація використовує алгоритм Fisher-Yates AKA Knuth Shuffle. Він працює в 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};
var rng = new Random();
rng.Shuffle(array);
rng.Shuffle(array); // different order from first call to Shuffle

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


1
Чи не було б краще змінити параметри і зробити використання на зразок array.Shuffle(new Random());..?
Кен Кін

Ви можете спростити своп, використовуючи Tuples з Framework 4.0 -> (масив [n], масив [k]) = (масив [k], масив [n]);
dynamichael

@Ken Kin: Ні, це було б погано. Причина полягає в тому, що new Random()вона ініціалізується із початковим значенням на основі поточного системного часу, яке оновлюється лише кожні ~ 16 мс.
Метт

У деяких швидких тестах цього рішення проти списку deleteAt є невелика різниця у 999 елементах. Різниця стає різкою в 99999 випадкових точок, при цьому рішення в 3 мс, а інше в 1810 мс.
галамдрінг

18

Ви шукаєте алгоритм перетасування, правда?

Гаразд, є два способи зробити це: розумні, але люди - завжди здаються нерозумілими-це-і-отримують-не-неправильно-так-можливо-не-що-то-розумним - адже так, і тупий-як-скелі-але-кого-хто піклується, тому що це працює.

Тупий шлях

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

Цей алгоритм працює добре, але переконайтесь, що ваш генератор випадкових чисел навряд чи позначить два рядки з однаковим числом. Через так званий парадокс дня народження це трапляється частіше, ніж можна було очікувати. Його часова складність становить O ( n log n ).

Розумний шлях

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

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

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

Ітераційний еквівалент - це пройти ітератор по масиву, обмінюючись випадковими елементами, коли ви йдете разом, але зауважте, що ви не можете поміняти місцями елементом після того, на який вказує ітератор. Це дуже поширена помилка і призводить до упереджених перебоїв.

Часова складність становить O ( n ).


8

Цей алгоритм простий, але не ефективний, O (N 2 ). Всі алгоритми "порядок за", як правило, 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 (N 2 ), є тонкою: List.RemoveAt () - це операція O (N), якщо ви не видаляєте її в порядку з кінця.


2
Це має такий самий ефект, як і змішання з кнутом, але це не так ефективно, оскільки передбачає депопуляцію одного списку та переселення іншого. Поміщення предметів на місці було б кращим рішенням.
Нік Джонсон

1
Я вважаю це елегантним і легко зрозумілим, і на 500 струнах це не має особливого значення ...
Sklivvz

4

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

   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>();

чому ви відтворюєте rng кожен виклик методу ... Ви заявляєте про це на рівні класу, але використовуєте його як локальний ...
Yaron

1

Рандомізація масиву є інтенсивним, оскільки вам доведеться зміщувати купу рядків. Чому б просто не випадковим чином прочитати з масиву? У гіршому випадку ви навіть можете створити клас обгортки за допомогою getNextString (). Якщо вам дійсно потрібно створити випадковий масив, ви можете зробити щось на кшталт

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

* 5 довільне.


Випадкове зчитування з масиву, ймовірно, кілька разів потрапить на деякі предмети та пропустить інші!
Рей Хейс

Алгоритм перетасування порушено. Вам доведеться зробити свої довільні 5 дуже високими, перш ніж ваше переміщення буде неупередженим.
Пітару

Складіть масив (цілих) індексів. Перемішайте індекси. Просто використовуйте індекси у тому випадковому порядку. Ніяких дублікатів, не перетасовування посилань на рядки в пам'яті (що може викликати кожне інтернування і що ні).
Крістофер

1

Думаючи, що ви думаєте, ви можете це зробити:

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

0

Створіть масив випадкових поплавків чи точок однакової довжини. Сортуйте цей масив і зробіть відповідні підкачки на своєму цільовому масиві.

Це дає справді незалежний сорт.


0
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

Жакко, ваше рішення - це нестандартний IComparer. Програми сортування вимагають, щоб порівняльник відповідав декільком вимогам, щоб нормально функціонувати. Перше серед них - консистенція. Якщо порівняльник викликається однією і тією ж парою об'єктів, він повинен завжди повертати один і той же результат. (порівняння також має бути транзитивним).

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

Що стосується рішень, які пов'язують випадкове числове значення з кожним записом, а потім сортують за цим значенням, вони призводять до притаманного зміщення виводу, оскільки будь-коли двом записам присвоюється однакове числове значення, випадковість виводу буде порушена. (У "стабільному" режимі сортування, що б не було першим у вході, буде першим у висновку. Array.Sort не буває стабільним, але все ще існує зміщення, засноване на розподілі, виконаному алгоритмом Quicksort).

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

Для перетасування списків пісень немає жодної проблеми з використанням насінного PRNG (наприклад, System.Random). Для покерного сайту це навіть не варіант, і вам потрібно думати про проблему набагато складніше, ніж хтось збирається зробити для вас під час stackoverflow. (використання криптографічного RNG - це лише початок. Вам потрібно переконатися, що ваш алгоритм не вводить упередженість, у вас є достатні джерела ентропії, і що ви не піддаєте жодного внутрішнього стану, який би загрожував подальшій випадковості).


0

На цю посаду вже вдало відповіли - використовуйте реалізацію Дурстенфельда від переходу "Фішер-Йейтс" для швидкого та неупередженого результату. Навіть були розміщені деякі реалізації, хоча зауважу, деякі з них є неправильними.

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


1
Ваші посилання досі порушені: /
Wai Ha Lee

0

Гаразд, це явно удар з мого боку (вибачаюся ...), але я часто використовую досить загальний і криптографічно сильний метод.

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

Вам не потрібні складні алгоритми.

Лише один простий рядок:

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

Зверніть увагу , що нам потрібно перетворити Arrayдо Listпершої, якщо не використовувати Listв першу чергу.

Також майте на увазі, що це не ефективно для дуже великих масивів! Інакше це чисто і просто.


Помилка: оператор '.' не можна застосовувати до операнду типу 'void'
корисноБережда

0

Це повне рішення робочої консолі на основі прикладу, наведеного тут :

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
        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();

-1

Ось простий спосіб використання OLINQ:

// Input array
List<String> lst = new List<string>();
for (int i = 0; i < 500; i += 1) lst.Add(i.ToString());

// Output array
List<String> lstRandom = new List<string>();

// Randomize
Random rnd = new Random();
lstRandom.AddRange(from s in lst orderby rnd.Next(100) select s);

-2
private ArrayList ShuffleArrayList(ArrayList source)
{
    ArrayList sortedList = new ArrayList();
    Random generator = new Random();

    while (source.Count > 0)
    {
        int position = generator.Next(source.Count);
        sortedList.Add(source[position]);
        source.RemoveAt(position);
    }  
    return sortedList;
}

Мені здається, ви могли б підвищити ефективність та читати, замість того, щоб намагатися перетасувати масив, оголосивши другий масив, краще спробувати перетворитись у список, перетасувати та повернутися до масиву:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
T_D,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.