André Alves de Lima

Talking about Software Development and more…

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]

22 thoughts on “Sorteio de números sem repetir em C# / VB.NET

  • 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…

    • andrealveslima disse:

      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

      • Clovis disse:

        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!!!

        • andrealveslima disse:

          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

  • Ezequiel disse:

    Olá, Obrigado André e Herbert por compartilharem, ótimas soluções!

    • andrealveslima disse:

      Olá Ezequiel! Fico feliz que tenha gostado do artigo!

      Abraço!
      André Lima

      • deuzivaldo disse:

        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

        • andrealveslima disse:

          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

  • […] 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). […]

  • deuzivaldo disse:

    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.

    • andrealveslima disse:

      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

  • deuzivaldo disse:

    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

  • deuzivaldo disse:

    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

    • andrealveslima disse:

      Olá Deuzivaldo!

      É só você colocar esse mesmo código dentro de um laço “For”.. Por exemplo, para gerar 5 combinações:

      For NumeroJogo As Integer = 0 To 4
      	' Código para gerar o jogo vem aqui...
      Next
      

      Abraço!
      André Lima

  • deuzivaldo disse:

    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

    • andrealveslima disse:

      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:

      Dim rnd As New Random()
      

      E coloque esta linha para fora do método:

      Private rnd As New Random()
      

      Abraço!
      André Lima

  • deuzivaldo disse:

    Bom dia professor deu certo o brigado
    Tenha um bom dia e um bom trabalho

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *