Qual é a melhor maneira de randomizar a ordem de uma lista genérica em C #? Eu tenho um conjunto finito de 75 números em uma lista à qual gostaria de atribuir uma ordem aleatória, para desenhá-los para um aplicativo do tipo loteria.
c#
generic-list
mirezus
fonte
fonte
Respostas:
Shuffle qualquer
(I)List
com um método de extensão baseado no shuffle de Fisher-Yates :Uso:
O código acima usa o método System.Random, muito criticado, para selecionar candidatos à troca. É rápido, mas não tão aleatório quanto deveria ser. Se você precisar de uma melhor qualidade de aleatoriedade em seus shuffles, use o gerador de números aleatórios em System.Security.Cryptography da seguinte forma:
Uma comparação simples está disponível neste blog (WayBack Machine).
Edit: Desde que escrevi esta resposta há alguns anos, muitas pessoas comentaram ou escreveram para mim, para apontar a grande falha boba da minha comparação. Claro que eles estão certos. Não há nada de errado com o System.Random se ele for usado da maneira que foi planejado. No meu primeiro exemplo acima, instanciamos a variável rng dentro do método Shuffle, que está solicitando problemas se o método for chamado repetidamente. Abaixo está um exemplo fixo e completo, com base em um comentário realmente útil recebido hoje de @weston aqui no SO.
Program.cs:
fonte
Random rng = new Random();
umstatic
resolveria o problema no post de comparação. Como cada chamada subsequente seguiria as anteriores, o último resultado aleatório.Se precisarmos embaralhar itens em uma ordem completamente aleatória (apenas para misturar os itens de uma lista), prefiro esse código simples, porém eficaz, que ordena itens por guia ...
fonte
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.csNewGuid
garante apenas que ele fornece um GUID exclusivo. Não garante a aleatoriedade. Se você estiver usando um GUID para outra finalidade que não seja a criação de um valor exclusivo , estará fazendo errado.Estou um pouco surpreso com todas as versões desajeitadas desse algoritmo simples aqui. Fisher-Yates (ou Knuth shuffle) é um pouco complicado, mas muito compacto. Por que é complicado? Porque você precisa prestar atenção se o gerador de números aleatórios
r(a,b)
retorna valor ondeb
é inclusivo ou exclusivo. Também editei a descrição da Wikipedia para que as pessoas não sigam cegamente o pseudocódigo e criem bugs difíceis de detectar. Para .Net,Random.Next(a,b)
retorna um número exclusivob
, sem mais delongas, veja como ele pode ser implementado em C # /. Net:Experimente este código .
fonte
i = list.Count - 1
, ou seja, a última iteração,rnd.Next(i, list.Count)
retornará a você. Portanto, você precisai < list.Count -1
como condição de loop. Bem, você não 'precisa', mas salva uma iteração;)Método de extensão para IEnumerable:
fonte
OrderBy
usa uma variante do QuickSort para classificar os itens por suas chaves (ostensivamente aleatórias). O desempenho do QuickSort é O (N log N) ; em contraste, um embaralhamento de Fisher-Yates é O (N) . Para uma coleção de 75 elementos, isso pode não ser um grande problema, mas a diferença será acentuada para coleções maiores.Random.Next()
pode produzir uma distribuição de valores razoavelmente pseudo-aleatória, mas não garante que os valores sejam únicos. A probabilidade de chaves duplicadas aumenta (não linearmente) com N até atingir certeza quando N atinge 2 ^ 32 + 1. OOrderBy
QuickSort é uma classificação estável ; portanto, se vários elementos receberem o mesmo valor de índice pseudo-aleatório, sua ordem na sequência de saída será a mesma que na sequência de entrada; assim, um viés é introduzido no "shuffle".A idéia é obter um objeto anônimo com o item e a ordem aleatória e, em seguida, reordenar os itens por esse pedido e retornar o valor:
fonte
fonte
var listCopy = list.ToList()
evitar colocar todos os itens da lista de entrada? Realmente não entendo por que você deseja alterar essas listas para esvaziar.EDIT O
RemoveAt
é uma fraqueza na minha versão anterior. Esta solução supera isso.Observe o opcional
Random generator
: se a implementação da estrutura baseRandom
não for segura para threads ou criptograficamente forte o suficiente para suas necessidades, você poderá injetar sua implementação na operação.Uma implementação adequada para uma implementação criptograficamente segura de thread-safe
Random
pode ser encontrada nesta resposta.Aqui está uma idéia, estenda o IList de uma maneira (esperançosamente) eficiente.fonte
GetNext
ouNext
?Você pode conseguir isso usando este método de extensão simples
e você pode usá-lo fazendo o seguinte
fonte
Random
instância da classe fora da função como umastatic
variável. Caso contrário, você poderá obter a mesma semente de randomização do cronômetro se chamado em sucessão rápida.Esse é o meu método preferido de reprodução aleatória quando é desejável não modificar o original. É uma variante do algoritmo "de dentro para fora" de Fisher – Yates que funciona em qualquer sequência enumerável (o tamanho de
source
não precisa ser conhecido desde o início).Este algoritmo também pode ser implementado através da atribuição de um intervalo de
0
aolength - 1
e desgastante aleatoriamente os índices trocando o índice escolhido aleatoriamente com o último índice até que todos os índices foram escolhidos exatamente uma vez. Esse código acima realiza exatamente a mesma coisa, mas sem a alocação adicional. O que é bem legal.Com relação à
Random
classe, é um gerador de números de uso geral (e, se eu estivesse na loteria, consideraria usar algo diferente). Ele também conta com um valor de semente baseado no tempo por padrão. Um pequeno alívio do problema é propagar aRandom
classe comRNGCryptoServiceProvider
ou você pode usar oRNGCryptoServiceProvider
método semelhante a este (veja abaixo) para gerar valores aleatórios de ponto flutuante duplo aleatoriamente escolhidos de maneira uniforme, mas a execução de uma loteria requer compreensão da aleatoriedade e da natureza de a fonte aleatória.O ponto de gerar um duplo aleatório (entre 0 e 1 exclusivamente) é usar para dimensionar para uma solução inteira. Se você precisar escolher algo de uma lista com base em um duplo aleatório
x
que sempre será,0 <= x && x < 1
é direto.Aproveitar!
fonte
Se você não se importa em usar dois
Lists
, provavelmente esta é a maneira mais fácil de fazê-lo, mas provavelmente não é a mais eficiente ou imprevisível:fonte
Se você tiver um número fixo (75), poderá criar uma matriz com 75 elementos e enumerar sua lista, movendo os elementos para posições aleatórias na matriz. Você pode gerar o mapeamento do número da lista para o índice da matriz usando o shuffle Fisher-Yates .
fonte
Eu costumo usar:
fonte
fonte
Aqui está um Shuffler eficiente que retorna uma matriz de bytes de valores aleatórios. Nunca embaralha mais do que o necessário. Pode ser reiniciado de onde parou anteriormente. Minha implementação real (não mostrada) é um componente MEF que permite a um shuffler de substituição especificado pelo usuário.
`
fonte
Aqui está uma maneira segura de thread para fazer isso:
fonte
fonte
Uma modificação simples da resposta aceita que retorna uma nova lista em vez de trabalhar no local e aceita o mais geral
IEnumerable<T>
como muitos outros métodos do Linq.fonte
Eu encontrei uma solução interessante online.
Cortesia: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
fonte
Post antigo, com certeza, mas eu apenas uso um GUID.
Um GUID é sempre único e, uma vez que é regenerado toda vez que o resultado muda a cada vez.
fonte
Uma abordagem muito simples para esse tipo de problema é usar várias trocas de elementos aleatórios na lista.
No pseudo-código, seria assim:
fonte