Melhor maneira de randomizar uma matriz com o .NET


141

Qual é a melhor maneira de randomizar uma matriz de seqüências de caracteres com o .NET? Minha matriz contém cerca de 500 strings e eu gostaria de criar um novo Arraycom as mesmas strings, mas em uma ordem aleatória.

Inclua um exemplo de C # na sua resposta.


1
Aqui está uma solução estranha, mas simples para isso - stackoverflow.com/a/4262134/1298685 .
Ian Campbell

1
Usando o MedallionRandom pacote NuGet, este é apenas myArray.Shuffled().ToArray()(ou myArray.Shuffle()se você quiser transformar a matriz atual)
ChaseMedallion

Respostas:


171

Se você usa o .NET 3.5, pode usar o seguinte frescor IEnumerable (VB.NET, não C #, mas a ideia deve ficar clara ...):

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

Edit: OK e aqui está o código VB.NET correspondente:

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

Segunda edição, em resposta às observações de que System.Random "não é seguro para threads" e "adequado apenas para aplicativos de brinquedo" devido ao retorno de uma sequência baseada em tempo: conforme usado no meu exemplo, Random () é perfeitamente seguro para threads, a menos que você está permitindo que a rotina na qual você aleatoriamente a matriz seja reinserida; nesse caso, você precisará de algo semelhante lock (MyRandomArray)para não corromper seus dados, o que protegerárnd .

Além disso, deve-se entender que o System.Random como fonte de entropia não é muito forte. Conforme observado na documentação do MSDN , você deve usar algo derivado System.Security.Cryptography.RandomNumberGeneratorse estiver fazendo algo relacionado à segurança. Por exemplo:

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

duas notas: 1) System.Random não é seguro para threads (você foi avisado) e 2) System.Random é baseado em tempo; portanto, se você usar esse código em um sistema altamente concorrente, é possível que duas solicitações obtenham o mesmo valor (ou seja, em
aplicativos da

2
Só para esclarecer o exposto, System.Random semearão si mesmo usando a hora atual, então duas instâncias criadas simultaneamente irá gerar o mesmo sequence..System.Random "aleatório" só deve ser usado em aplicativos de brinquedo
therealhoff

8
Além disso, esse algoritmo é O (n log n) e influenciado pelo algoritmo Qsort. Veja minha resposta para uma solução O (n) imparcial.
21811 Matt Howells

9
A menos que OrderByarmazene em cache as chaves de classificação internamente, isso também tem o problema de violar a propriedade transitiva das comparações ordenadas. Se houver alguma verificação no modo de depuração que OrderByproduza resultados corretos, em teoria, isso poderá gerar uma exceção.
21310 Sam Samwell


205

A implementação a seguir usa o algoritmo Fisher-Yates AKA, o Knuth Shuffle. Ele é executado no tempo O (n) e é embaralhado no lugar, por isso tem melhor desempenho do que a técnica 'ordenar aleatoriamente', embora haja mais linhas de código. Veja aqui algumas medidas de desempenho comparativas. Eu usei System.Random, o que é bom para fins não criptográficos. *

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

Uso:

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

* Para matrizes mais longas, para tornar igualmente provável o número (extremamente grande) de permutações, seria necessário executar um gerador de números pseudo-aleatórios (PRNG) através de muitas iterações para cada troca para produzir entropia suficiente. Para uma matriz de 500 elementos, apenas uma fração muito pequena dos 500 possíveis! É possível obter permutações usando um PRNG. No entanto, o algoritmo Fisher-Yates é imparcial e, portanto, o shuffle será tão bom quanto o RNG usado.


1
Não seria melhor alterar os parâmetros e fazer o uso como array.Shuffle(new Random());..?
Ken Kin

Você pode simplificar a troca usando Tuplas na estrutura 4.0 -> (matriz [n], matriz [k]) = (matriz [k], matriz [n]);
dynamichael

@ Kin Kin: Não, isso seria ruim. O motivo é que ele new Random()é inicializado com um valor inicial baseado na hora atual do sistema, que atualiza apenas a cada ~ 16ms.
Matt Howells

Em alguns testes rápidos dessa solução removeAt da lista, há uma pequena diferença nos 999 elementos. A diferença é drástica em 99999 ints aleatórios, com esta solução em 3ms e a outra em 1810ms.
galamdring

18

Você está procurando um algoritmo de embaralhamento, certo?

Ok, existem duas maneiras de fazer isso: o inteligente, mas as pessoas sempre parecem entender mal e entender errado, então talvez o seu não seja tão inteligente depois de tudo maneira, e a maneira idiota como pedras, mas quem se importa porque funciona.

Maneira burra

  • Crie uma duplicata da sua primeira matriz, mas marque cada sequência com um número aleatório.
  • Classifique a matriz duplicada em relação ao número aleatório.

Esse algoritmo funciona bem, mas verifique se é improvável que seu gerador de números aleatórios identifique duas strings com o mesmo número. Por causa do chamado Paradoxo de Aniversário , isso acontece com mais frequência do que você imagina. Sua complexidade de tempo é O ( n log n ).

Maneira inteligente

Vou descrever isso como um algoritmo recursivo:

Para embaralhar uma matriz de tamanho n (índices no intervalo [0 .. n -1]):

E se n = 0
  • fazer nada
E se n > 0
  • (passo recursivo) embaralhe o primeiro n -1 elementos da matriz
  • escolha um índice aleatório, x , no intervalo [0 .. n -1]
  • troque o elemento no índice n -1 pelo elemento no índice x

O equivalente iterativo é percorrer um iterador pela matriz, trocando com elementos aleatórios à medida que avança, mas observe que você não pode trocar com um elemento depois daquele que o iterador aponta. Este é um erro muito comum e leva a um embaralhamento parcial.

A complexidade do tempo é O ( n ).


8

Esse algoritmo é simples, mas não eficiente, O (N 2 ). Todos os algoritmos "ordenar por" são normalmente O (N log N). Provavelmente não faz diferença abaixo de centenas de milhares de elementos, mas faria para grandes listas.

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

A razão pela qual é O (N 2 ) é sutil: List.RemoveAt () é uma operação O (N), a menos que você remova a ordem do final.


2
Isso tem o mesmo efeito que um shuffle knuth, mas não é tão eficiente, pois envolve despovoar uma lista e repovoar outra. Trocar itens no local seria uma solução melhor.
Nick Johnson

1
Acho isso elegante e de fácil compreensão e em 500 cordas não faz um pouco de diferença ...
Sklivvz

4

Você também pode criar um método de extensão com Matt Howells. Exemplo.

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

Então você pode usá-lo como:

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

por que você está recriando rng todas as chamadas para o método ... Você o declara no nível da classe, mas o usa como local ...
Yaron

1

A randomização da matriz é intensiva, pois você precisa mudar um monte de strings. Por que não apenas ler aleatoriamente a partir da matriz? No pior dos casos, você pode até criar uma classe de wrapper com um getNextString (). Se você realmente precisar criar uma matriz aleatória, poderá fazer algo como

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

O * 5 é arbitrário.


É provável que uma leitura aleatória da matriz atinja alguns itens várias vezes e perca outros!
Ray Hayes

O algoritmo de reprodução aleatória está quebrado. Você teria que aumentar muito o seu arbitrário 5 antes que seu shuffle seja imparcial.
Pitarou 20/09/08

Faça uma matriz dos índices (inteiro). Embaralhe os índices. Basta usar os índices nessa ordem aleatória. Sem duplicatas, sem embaralhar as referências de strings na memória (que podem disparar internamente e quais não).
Christopher

1

Pensando em cima da minha cabeça, você poderia fazer o seguinte:

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

Gere uma matriz de flutuadores aleatórios ou ints do mesmo comprimento. Classifique essa matriz e faça trocas correspondentes na matriz de destino.

Isso produz um tipo verdadeiramente independente.


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

Jacco, sua solução é um IComparer personalizado não é seguro. As rotinas de classificação exigem que o comparador esteja em conformidade com vários requisitos para funcionar corretamente. O primeiro deles é a consistência. Se o comparador for chamado no mesmo par de objetos, sempre deverá retornar o mesmo resultado. (a comparação também deve ser transitiva).

O não cumprimento desses requisitos pode causar vários problemas na rotina de classificação, incluindo a possibilidade de um loop infinito.

Em relação às soluções que associam um valor numérico aleatório a cada entrada e, em seguida, classificam por esse valor, elas levam a um viés inerente na saída porque sempre que duas entradas recebem o mesmo valor numérico, a aleatoriedade da saída fica comprometida. (Em uma rotina de classificação "estável", o que ocorrer primeiro na entrada será o primeiro na saída. Array.Sort não é estável, mas ainda existe um viés com base no particionamento realizado pelo algoritmo Quicksort).

Você precisa pensar um pouco sobre o nível de aleatoriedade necessário. Se você está executando um site de pôquer no qual precisa de níveis criptográficos de aleatoriedade para se proteger contra um invasor determinado, possui requisitos muito diferentes de alguém que deseja apenas aleatoriamente uma lista de reprodução de músicas.

Para a reprodução aleatória da lista de músicas, não há problema em usar um PRNG inicial (como System.Random). Para um site de pôquer, nem sequer é uma opção e você precisa pensar muito mais sobre o problema do que qualquer um pode fazer por você no stackoverflow. (usar um RNG criptográfico é apenas o começo, é necessário garantir que seu algoritmo não introduza um viés, que você tenha fontes suficientes de entropia e que não exponha nenhum estado interno que comprometa a aleatoriedade subsequente).


0

Este post já foi muito bem respondido - use uma implementação de Durstenfeld do embaralhamento Fisher-Yates para obter um resultado rápido e imparcial. Houve até algumas implementações publicadas, embora note que algumas estão realmente incorretas.

Há algum tempo, escrevi algumas postagens sobre a implementação de shuffles completos e parciais usando essa técnica e (neste segundo link, espero acrescentar valor), também um post de acompanhamento sobre como verificar se sua implementação é imparcial , que pode ser usado para verificar qualquer algoritmo de reprodução aleatória. Você pode ver no final do segundo post o efeito de um simples erro na seleção de números aleatórios.


1
Seus links ainda estão quebrados: /
Wai Ha Lee

0

Ok, isso é claramente um problema do meu lado (pede desculpas ...), mas geralmente uso um método bastante geral e criptograficamente forte.

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 () é uma extensão em qualquer IEnumerable, portanto é possível obter, digamos, números de 0 a 1000 em ordem aleatória em uma lista com

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

Esse método também não surpreende quando se trata de classificação, uma vez que o valor da classificação é gerado e lembrado exatamente uma vez por elemento na sequência.


0

Você não precisa de algoritmos complicados.

Apenas uma linha simples:

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

Observe que precisamos converter o primeiro Arraypara um List, se você não usarList em primeiro lugar.

Além disso, lembre-se de que isso não é eficiente para matrizes muito grandes! Caso contrário, é limpo e simples.


Erro: Operador '.' não pode ser aplicada ao operando do tipo 'vazio'
usefulBee

0

Esta é uma solução completa de console de trabalho com base no exemplo fornecido aqui :

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

Aqui está uma maneira simples de usar 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);

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

Para mim, parece que você poderia aumentar a eficiência e a capacidade de leitura, em vez de tentar embaralhar uma matriz declarando uma segunda matriz, é melhor tentar converter em uma lista, embaralhar e voltar a uma matriz:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
T_D
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.