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

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

Включите в свой ответ пример C#.

Вот странное, но простое решение для этого - stackoverflow.com/a/4262134/1298685.

Ian Campbell 30.05.2014 04:29

Используя пакет NuGet Медальон, это просто myArray.Shuffled().ToArray() (или myArray.Shuffle(), если вы хотите изменить текущий массив)

ChaseMedallion 07.05.2016 17:46

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

Herohtar 09.08.2020 08:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
152
3
158 918
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

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

Это дает действительно независимую сортировку.

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

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

* 5 произвольно.

Случайное чтение из массива может поразить одни элементы несколько раз и пропустить другие!

Ray Hayes 20.09.2008 21:41

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

Pitarou 20.09.2008 22:12

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

Christopher 27.08.2017 08:36

Этот алгоритм прост, но неэффективен, 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), если вы не удалите ее по порядку с конца.

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

Nick Johnson 20.09.2008 23:06

Я нахожу это элегантным и легко понятным, и на 500 струнах это не имеет никакого значения ...

Sklivvz 20.09.2008 23:41
Ответ принят как подходящий

Если вы используете .NET 3.5, вы можете использовать следующие преимущества IEnumerable:

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 зависит от времени, поэтому, если вы используете этот код в сильно параллельной системе, два запроса могут получить то же значение (например, в веб-приложениях)

therealhoff 21.09.2008 01:37

Чтобы прояснить вышесказанное, System.Random будет заполнять себя, используя текущее время, поэтому два экземпляра, созданные одновременно, будут генерировать одну и ту же "случайную" последовательность. System.Random следует использовать только в игрушечных приложениях.

therealhoff 21.09.2008 01:41

Также этот алгоритм O (n log n) и смещен алгоритмом Qsort. См. Мой ответ на объективное решение O (n).

Matt Howells 21.09.2008 13:02

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

Sam Harwell 16.10.2009 20:22

Обновлено: OrderBy фактически кеширует ключи. Однако я не рассматриваю это как часть общедоступной спецификации, поэтому нет гарантии, что все реализации будут делать это.

Sam Harwell 16.10.2009 20:56

Смотрите это: blogs.msdn.com/b/ericlippert/archive/2011/01/31/… и en.wikipedia.org/wiki/…

Gregor Slavec 01.02.2011 18:11

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

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

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

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

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

Тупой путь

  • Create a duplicate of your first array, but tag each string should with a random number.
  • Sort the duplicate array with respect to the random number.

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

Умный способ

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

To shuffle an array of size n (indices in the range [0..n-1]):

if n = 0
  • do nothing
if n > 0
  • (recursive step) shuffle the first n-1 elements of the array
  • choose a random index, x, in the range [0..n-1]
  • swap the element at index n-1 with the element at index x

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

Сложность по времени - O (п).

Вот простой способ использования 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);

Следующая реализация использует Алгоритм Фишера-Йейтса 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

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

Не лучше ли изменить параметры и использовать как array.Shuffle(new Random()); ..?

Ken Kin 10.05.2019 20:13

Вы можете упростить обмен с помощью кортежей, начиная с версии 4.0 -> (array [n], array [k]) = (array [k], array [n]);

dynamichael 17.06.2019 03:31

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

Matt Howells 26.06.2019 14:20

В некоторых быстрых тестах этого решения по сравнению со списком removeAt есть небольшая разница в 999 элементов. Разница становится существенной при 99999 случайных целых числах, причем это решение составляет 3 мс, а другое - 1810 мс.

galamdring 30.11.2019 15:04

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

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

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

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

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

На этот пост уже был дан довольно хороший ответ - используйте реализацию Дарстенфельда перетасовки Фишера-Ятса для быстрого и объективного результата. Были даже опубликованы некоторые реализации, хотя я заметил, что некоторые на самом деле неверны.

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

Ваши ссылки все еще не работают: /

Wai Ha Lee 04.01.2019 23:36

Вы также можете сделать метод расширения из Matt Howells. Пример.

   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 13.01.2020 22:31

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 21.02.2016 14:43

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

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

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

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

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

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

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

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

Ошибка: оператор "." не может применяться к операнду типа 'void'

usefulBee 13.04.2016 18:33

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

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

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

Другие вопросы по теме