19 03 2014
Sorteio de números sem repetir em C# / VB.NET
Ontem eu me deparei com uma questão no fórum da MSDN em que o usuário precisava, dado um intervalo de números (mínimo / máximo), realizar o sorteio de uma quantidade específica de números dentro desse intervalo, mas, de forma que os números sorteados não se repetissem. O usuário chegou a uma versão que estava funcionando, mas, o algoritmo não sorteava a quantidade correta de números. Então, resolvi tirar um tempinho aqui e escrever um algoritmo que solucionasse o problema dele. E como talvez esse exemplo possa ajudar alguém no futuro, resolvi postá-lo aqui no blog.
A ideia é simples. Criamos um método que recebe três argumentos: quantidade (de números a serem sorteados), minimo (menor número do intervalo permitido) e maximo (maior número do intervalo permitido):
private static int[] SorteiaNumerosSemRepetir(int quantidade, int minimo, int maximo) { List<int> numerosSorteados = new List<int>(); return numerosSorteados.ToArray(); }
Private Function SorteiaNumerosSemRepetir(quantidade As Integer, minimo As Integer, maximo As Integer) As Integer() Dim numerosSorteados As New List(Of Integer)() Return numerosSorteados.ToArray() End Function
Logo de cara podemos implementar algumas validações nos argumentos, de forma que, caso eles sejam inválidos, lançamos um ArgumentException. As validações são:
– Quantidade deve ser maior que zero
– Máximo deve ser maior que mínimo
– Quantidade deve ser menor que a diferença entre máximo e mínimo (afinal, não dá pra sortear mais números sem repetir do que o intervalo possibilita)
private static int[] SorteiaNumerosSemRepetir(int quantidade, int minimo, int maximo) { // Validações dos argumentos. if (quantidade < 0) throw new ArgumentException("Quantidade deve ser maior que zero."); else if (quantidade > maximo + 1 - minimo) throw new ArgumentException("Quantidade deve ser menor do que a diferença entre máximo e mínimo."); else if (maximo <= minimo) throw new ArgumentException("Máximo deve ser maior do que mínimo."); List<int> numerosSorteados = new List<int>(); return numerosSorteados.ToArray(); }
Private Function SorteiaNumerosSemRepetir(quantidade As Integer, minimo As Integer, maximo As Integer) As Integer() ' Validações dos argumentos. If quantidade < 0 Then Throw New ArgumentException("Quantidade deve ser maior que zero.") ElseIf quantidade > maximo + 1 - minimo Then Throw New ArgumentException("Quantidade deve ser menor do que a diferença entre máximo e mínimo.") ElseIf maximo <= minimo Then Throw New ArgumentException("Máximo deve ser maior do que mínimo.") End If Dim numerosSorteados As New List(Of Integer)() Return numerosSorteados.ToArray() End Function
Feito isso, podemos seguir com a implementação do algoritmo de fato. A ideia é bem simples. Primeiro criamos uma instância de Random que vai servir para fazer o sorteio aleatório. Depois, criamos um “while” que vai ser executado enquanto a quantidade de números sorteados for menor do que a quantidade informada no argumento do método. E então, dentro do “while“, sorteamos números até que o número sorteado ainda não tenha sido sorteado anteriormente:
private static int[] SorteiaNumerosSemRepetir(int quantidade, int minimo, int maximo) { // Validações dos argumentos. if (quantidade < 0) throw new ArgumentException("Quantidade deve ser maior que zero."); else if (quantidade > maximo + 1 - minimo) throw new ArgumentException("Quantidade deve ser menor do que a diferença entre máximo e mínimo."); else if (maximo <= minimo) throw new ArgumentException("Máximo deve ser maior do que mínimo."); List<int> numerosSorteados = new List<int>(); Random rnd = new Random(); while (numerosSorteados.Count < quantidade) { int numeroSorteado = rnd.Next(minimo, maximo + 1); // Número já foi sorteado? Então sorteamos novamente até o número não ter sido sorteado ainda. while (numerosSorteados.Contains(numeroSorteado)) numeroSorteado = rnd.Next(minimo, maximo + 1); numerosSorteados.Add(numeroSorteado); } return numerosSorteados.ToArray(); }
Private Function SorteiaNumerosSemRepetir(quantidade As Integer, minimo As Integer, maximo As Integer) As Integer() ' Validações dos argumentos. If quantidade < 0 Then Throw New ArgumentException("Quantidade deve ser maior que zero.") ElseIf quantidade > maximo + 1 - minimo Then Throw New ArgumentException("Quantidade deve ser menor do que a diferença entre máximo e mínimo.") ElseIf maximo <= minimo Then Throw New ArgumentException("Máximo deve ser maior do que mínimo.") End If Dim numerosSorteados As New List(Of Integer)() Dim rnd As New Random() While numerosSorteados.Count < quantidade Dim numeroSorteado As Integer = rnd.[Next](minimo, maximo + 1) ' Número já foi sorteado? Então sorteamos novamente até o número não ter sido sorteado ainda. While numerosSorteados.Contains(numeroSorteado) numeroSorteado = rnd.[Next](minimo, maximo + 1) End While numerosSorteados.Add(numeroSorteado) End While Return numerosSorteados.ToArray() End Function
Simples, não? Você pode testar o método em uma Console Application da seguinte forma:
static void Main(string[] args) { // Sorteando dez números que estejam entre o intervalo de 1 e 10. Console.WriteLine(string.Join(", ", SorteiaNumerosSemRepetir(10, 1, 10))); Console.ReadLine(); }
Sub Main() ' Sorteando dez números que estejam entre o intervalo de 1 e 10. Console.WriteLine(String.Join(", ", SorteiaNumerosSemRepetir(10, 1, 10))) Console.ReadLine() End Sub
Mas, atenção!! Esse algoritmo não é o mais performático do mundo para realizar essa tarefa. Ele é bem simples, mas, caso você vá trabalhar com uma quantidade muito grande de números a serem sorteados, vale a pena investigar uma maneira mais otimizada. Portanto, use por sua própria conta e risco, e com moderação!
Caso queira baixar o exemplo, vocês podem encontra-lo no meu repositório do GitHub.
[Edit]
Como vocês podem ver nos comentários, o Herbert Lausmann sugeriu uma solução alternativa, utilizando os métodos Range da classe Enumerable com um método Shuffle para coleções. Não sei qual das duas opções seria mais performática, mas, de qualquer forma vale a pena conferir.
// 0 é o valor mínimo e 100 o máximo // Será retornada uma lista com os números entre entre 1 e 100 ordenados aleatoriamente... var numbers = Enumerable.Range(1, 100).Shuffle().ToList();
Até a próxima dica!
André Lima
[Photo credit: Howard Lake]
Erro no Visual Studio 2013 após Windows Update: Microsoft.VisualStudio.Editor.Implementation.EditorPackage did not load correctly Problemas no modo apresentador do Visual Studio após instalar o ReSharper 8.1
Olá André,
Achei seu post interessante e gostaria de compartilhar uma outra maneira de fazer isso:
var numbers = Enumerable.Range(1, 100).Shuffle().ToList();
// 0 é o valor mínimo e 100 o máximo
// Será retornada uma lista com os números entre entre 1 e 100 ordenados aleatoriamente…
Olá Herbert, obrigado pelo comentário.
Editei o post e adicionei sua sugestão como uma outra solução que pode ser utilizada nesse cenário.
André Lima
Pow cara nem consegui dar print nesse codigo… Essas porcarias de scrolls so atrapalham… Melhor colocar com quebra fe linha… Palhaçada isso de colocar codigo em scroll!!!
Olá Clovis!
Obrigado pelo feedback, porém, essa é maneira mais utilizada de colocar código fonte em artigos.. Talvez você não saiba, mas se você quiser copiar o código, é só clicar duas vezes no bloco e ele já selecionará o código todo..
Abraço!
André Lima
Olá, Obrigado André e Herbert por compartilharem, ótimas soluções!
Olá Ezequiel! Fico feliz que tenha gostado do artigo!
Abraço!
André Lima
boa tarde andré gostei consegui fazer com os numeros
mais na verdade eu gostaria era de altera a cor das label sorteadas
eu consegui mas as vezes so muda a cor de 4 ou 3
este e o cod que esto usando em um buttum
Dim r As Random = New Random
For i As Integer = 1 To 5
Me.Controls(“label” & r.Next(1, 20)).BackColor = Color.LightGreen
Next
obrigado
Olá Deuzivaldo!
Não entendi.. Você tem 20 labels no formulário e quer sortear 5, é isso? E qual é o problema que está acontecendo com o código que você enviou? Se quiser postar o seu projeto no Dropbox ou Onedrive para eu dar uma olhada, me manda o link para baixar no meu e-mail: contato [arroba] andrealveslima [ponto] com [ponto] br
Abraço!
André Lima
boa tarde andre e iso mesmo quero altera o backcolor de 5 label mais avezes so muda a cor de 4 ou 3
ok vou le enviar
Olá Deuzivaldo!
Recebi aqui o link.. Preciso de permissão na pasta onde você salvou o arquivo no Google Drive.. Fico no aguardo..
Abraço!
André Lima
[…] 4) Sorteio de números sem repetir em C# / VB.NET Antigamente eu acompanhava os fóruns da MSDN de forma frenética. Nos últimos anos eu decidi mudar um pouco o foco (passando a escrever mais artigos ao invés de ficar respondendo questões nos fóruns), mas, mesmo assim, de vez em quando eu dou uma passada nos fóruns da MSDN para ter ideias de posts para escrever. A ideia para esse artigo, que mostra como sortear números sem repetir em C# e VB.NET, surgiu de uma questão dos fóruns da MSDN. Como eu lembro de ter topado no passado com várias pessoas com essa mesma dúvida, resolvi escrever um artigo demonstrando como realizar essa tarefa. Aproveite e confira também os comentários do post, onde o Herbert Lausmann sugeriu uma outra maneira de resolver esse mesmo problema (ainda não sei qual das duas maneiras é a melhor, mas, vale a pena testar as duas e analisar qual se encaixa melhor na sua situação). […]
Bom dia a todos os leitores
Hoje venho a agradecer primeiro a Deus e depois a uma grande pessoa que se chama André Alves de Lima. Para mim ele é o meu professor, aprendi muito com ele, e por não ter deixado eu perder a esperança de procurar, encontrar alguém para aprender a programar, pois onde moro não tem quem ajude. Comprei vários curssos, mas do que adianta se não tem quem lhe ensine? Obrigado professor André, por te paciência de me ajudar e ajudar outras pessoas. Às veses pensava que você não ia responder o meu e-mail, mas quando eu abria o meu e-mail que via seu nome, eu já tinha uma certeza que a resposta certa acabava de chegar. Muito obrigado, que Deus mantenha sempre esta pessoa que você é. Agora tenho você para tirar-me qualquer dúvida.
Abraços.
Olá Deuzivaldo! Muito obrigado por essas belas palavras.. Fico feliz por ter conseguido te ajudar.. Qualquer coisa é só entrar em contato.. Abraço!
André Lima
Desculpe-me professor, eu estava comemorando o que você fez por mim, só agora me toquei que tem frases que escrevi errado.
Vou corrigir na próxima
Obrigado
Olá Deuzivaldo! Sem stress.. Sempre que possível eu corrijo os comentários antes de publicar.. Abraço!
André Lima
Bom dia professor mais uma vês vim posta uma duvida
Eu usei este seu código da seguinte maneira
Dim Numeros As New List(Of Integer)
For Each Numero As Integer In Numeros
Next
For Each Numero In Sorteia_Numeros_Sem_Repetir(Int(5), 1, 15)
Numeros.Add(Numero)
Next
For Each Numero In Numeros.Count.ToString
Dim Linha_Gride = ListView1.Items.Add(Numeros(0).ToString.PadLeft(2, “0”))
For ContadorNumero = 1 To Numeros.Count – 1
Linha_Gride.SubItems.Add(Numeros(ContadorNumero).ToString.PadLeft(2, “0”))
Next
Next
Bom ate ai tudo bem mais ele gera um jogo a cada click
Como poso cria um laço para gera mais de um jogo
Obrigado e bom dia
Olá Deuzivaldo!
É só você colocar esse mesmo código dentro de um laço “For”.. Por exemplo, para gerar 5 combinações:
Abraço!
André Lima
Bom dia professor eu já fiz isso
Só, que ele repete os mesmos números eu gostaria que ele sorteasse outros números novos.
Bom dia e obrigado
Olá Deuzivaldo!
Se você sempre está tendo os mesmos resultados, provavelmente é porque a variável “rnd” está sendo instanciada novamente todas as vezes.. Para não ter sempre o mesmo resultado, você precisa declarar ela para fora do método.. Ou seja, remova esta linha de dentro do método:
E coloque esta linha para fora do método:
Abraço!
André Lima
Bom dia professor deu certo o brigado
Tenha um bom dia e um bom trabalho
Olá Deuzivaldo! Que bom que funcionou.. Qualquer coisa estamos aí..
Abraço!
André Lima