Select Menu



por Alessandro Trovato

» » » » » » VBA - Dúvida dos leitores 001 - Gerando combinações de números
«
Proxima
Postagem mais recente
»
Anterior
Postagem mais antiga


Gerando combinações de números

A leitora Eliana Miranda nos mandou a seguinte dúvida:

"Gostaria de saber se é possível e como faço para o excel listar todas as combinações possíveis (sem repetições) compostas por 4 números de um conjunto de 62 números.
Isto é: eu tenho um conjunto de números que vai do 23 ao 80, mais o 87, o 88, o 90 e o 96.
Pretendo que o excel liste todas as combinações possíveis compostas por 4 números. Ex: 23-24-25-26, 23-24-26-25, 23-25-24-26, etc...O objectivo é, posteriormente, aplicar a formula (a/b)x(c/d) à listagem obtida.Obrigada"


É possível sim gerar combinações de números como essa. Primeiramente precisamos entender como o processo será feito.

Cada número precisará acontecer uma única vez na combinação, sem repetição. Para isso precisaremos de quatro estruturas de repetição (ou laços). Para ter todos os números em todas as quatro posições possíveis, precisaremos que todos os laços sejam do primeiro até o último número. Para evitar repetição, precisaremos conferir se o valor do número obtido é igual a um dos obtidos nos laços externos.

Desta forma, cada número será obtido no começo do laço e então será conferido se é igual a um dos outros laços (exceto no primeiro, por não haver necessidade). Somente no laço mais interno, após termos os quatro números, é que preencheremos os dados. Assim, teremos a seguinte estrutura idealizada:

    For Laco1 = 1 To [último valor]
        Valor1 = [dado]
        For Laco2 = 1 To [último valor]
            Valor2 = [dado]
            If Valor2 = Valor1 Then
                [pula para o próximo valor em Laco2]
            End If
            For Laco3 = 1 To [último valor]
                Valor3 = [dado]
                If Valor3 = Valor1 Or Valor3 = Valor2 Then
                    [pula para o próximo valor em Laco3]
                End If
                For Laco4 = 1 To [último valor]
                    Valor4 = [dado]
                    If Valor4 = Valor1 Or Valor4 = Valor2 Or Valor4 = Valor3 Then
                        [pula para o próximo valor em Laco4]
                    End If
                    [grava valores]
                Next Laco4
            Next Laco3
        Next Laco2
    Next Laco1

Entretanto, devemos observar que a quantidade de combinações a ser gerada é muito elevada. Se a combinação fosse de apenas três números, teríamos 61*60*59 combinações únicas (sem repetir um número na combinação), o que resulta em 215.940 combinações. Para adicionar mais um número à combinação, multiplicamos por 58 e obtemos 12.524.520, que é a quantidade de linhas que deverá ser gerada, muito acima dos 1.048.576 linhas que a planilha do Excel tem (e que muito julgam ser inalcançável).

Precisaremos então gerar uma "quebra de página". Podemos gerar essa quebra  quando chegar na última linha da planilha ou a cada uma determinada quantidade de valores do primeiro laço. Se optarmos pela primeira opção, teremos uma estrutura de verificação no laço mais interno (Laco4 acima) e será executada em todas as milhões de linha. Na segunda opção, a estrutura de verificação ficará no laço mais externo (Laco1 acima), que será executado nas 62 vezes que entrar no laço. Portanto, em termos de desempenho, a segunda opção será muito mais eficiente.

Como vamos estruturar essa quebra? Antes vamos definir onde irão ficar os números para gerar as combinações e as colunas com os dados resultantes. Vamos colocar os dados de origem na coluna A e por os números combinados nas colunas C, D, E e F, bem como a fórmula desejada na coluna G. Vamos manter uma linha de distância entre cada "bloco" de dados e começar a segunda "página" nas colunas I a M, a terceira nas colunas O a S e assim por diante. Ou seja, cada bloco começará 6 colunas adiante.

Vamos usar umas variáveis para auxiliar no posicionamento das colunas. Precisamos de apenas uma para a primeira coluna do bloco, as outras colunas podem usar este valor para posicionar. Como cada bloco está a 6 colunas de distância, então teremos de multiplicar a variável por 6. A posição inicial é a coluna C (terceira coluna), portanto temos de somar 3 à multiplicação. Porém, para que o primeiro bloco comece de fato na coluna C, é preciso inicializar o bloco com 0. Vamos à fórmula em código para compreender a ideia:

Posicao = Bloco * 6 + 3

Quando Bloco for 0, Posicao será 3, ou seja, coluna C. Quando Bloco for 1, Posicao será 9, que é a coluna I. E quando Bloco for 2, Posicao será 15, coluna O. Os blocos subsequentes seguirão sendo montados a cada 6 colunas. Portanto, esta fórmula resolve a questão do posicionamento da primeira coluna de cada bloco.

Já temos como gerar os blocos de dados. Falta criar a validação da quebra de bloco. Vamos fazer uma conta:

215.940 * 4 = 863.760
215.940 * 5 = 1.079.700 => passou das 1.048.576 linhas existentes em cada planilha do Excel.

Como vimos no cálculo acima, precisamos quebrar o bloco após 4 execuções para não passar do limite de linhas. Sabemos que um número é divisível por outro quando o resto é zero, portanto podemos usar esse método para dividir os blocos de 4 em 4. Veja o código:

If Laco1  Mod 4 = 0 Then

Assim, quando Laco1 for divisível por 4 será efetuada uma quebra. Porém, o laço começa em 1. Se analisarmos bem (fazendo teste de mesa), o primeiro bloco terá somente os três primeiros valores, enquanto o restante terá 4. Quando Laco1 for entre 1 e 3, os dados serão gravados no primeiro bloco, quando chegar a 4 vai pular para o segundo bloco. Para resolver isto subtraímos 1 de Laco1 um na verificação para começar a contagem no 0.

If (Laco1 - 1) Mod 4 = 0 Then

Agora sim, todos os blocos terão 4 números na primeira coluna. O posicionamento das colunas seguintes é fácil, Posicao + 1, Posicao + 2 etc.

Por fim, podemos melhorar o desempenho desabilitando o cálculo automático das fórmulas com a linha a seguir:

Application.Calculation = xlCalculationManual

Não esquecendo de habilitar depois ao término da execução:

Application.Calculation = xlCalculationAutomatic

Com toda a teoria devidamente explicada, é hora do código pronto:

Option Explicit
Sub FazerCombinacoes()

    Dim Quantidade As Integer
    Dim Linha As Long
    Dim Bloco As Long
    Dim Posicao As Long
    Dim Laco1 As Integer
    Dim Laco2 As Integer
    Dim Laco3 As Integer
    Dim Laco4 As Integer
    Dim Valor1 As Integer
    Dim Valor2 As Integer
    Dim Valor3 As Integer
    Dim Valor4 As Integer
    Application.Calculation = xlCalculationManual
    
    Quantidade = Range("A1:A62").Count
    Bloco = -1
    Linha = 1
    Posicao = 3

    For Laco1 = 1 To Quantidade
        If (Laco1 - 1) Mod 4 = 0 Then
            Bloco = Bloco + 1
            Linha = 1
            Posicao = Bloco * 6 + 3
            Debug.Print "Início do bloco " & (Bloco + 1) & ": " & Now
        End If
        Valor1 = Cells(Laco1, 1).Value
        For Laco2 = 1 To Quantidade
            Valor2 = Cells(Laco2, 1).Value
            If Valor2 = Valor1 Then
                GoTo SaiLaco2
            End If
            For Laco3 = 1 To Quantidade
                Valor3 = Cells(Laco3, 1).Value
                If Valor3 = Valor1 Or Valor3 = Valor2 Then
                    GoTo SaiLaco3
                End If
                For Laco4 = 1 To Quantidade
                    Valor4 = Cells(Laco4, 1).Value
                    If Valor4 = Valor1 Or Valor4 = Valor2 Or Valor4 = Valor3 Then
                        GoTo SaiLaco4
                    End If
                    Cells(Linha, Posicao).Value = Valor1
                    Cells(Linha, Posicao + 1).Value = Valor2
                    Cells(Linha, Posicao + 2).Value = Valor3
                    Cells(Linha, Posicao + 3).Value = Valor4
                    Cells(Linha, Posicao + 4).Formula = "=(" + _
                        Cells(Linha, Posicao).Address + "/" + _
                        Cells(Linha, Posicao + 1).Address + ")*(" + _
                        Cells(Linha, Posicao + 2).Address + "/" + _
                        Cells(Linha, Posicao + 3).Address + ")"
                    Linha = Linha + 1
                    Application.StatusBar = "Bloco: " & (Bloco + 1) & " / Linha: " & Linha
SaiLaco4:
                Next Laco4
                DoEvents
SaiLaco3:
            Next Laco3
SaiLaco2:
        Next Laco2
    Next Laco1

    Application.StatusBar = False
    Application.Calculation = xlCalculationAutomatic
    Debug.Print "Término do processamento: " & Now


End Sub

Observe que a variável Bloco foi inicializado com -1 para ficar com valor 0 na primeira execução do laço mais externo. Veja também que coloquei para mostrar o horário quando inicia cada bloco e quando termina o processamento, para ajudar a ter uma estimativa de quanto tempo leva para executar tudo. Esses horários serão exibidos na área Verificação imediata do editor VBA.

Perceba que o código está "engessado", sendo específico para o caso apresentado, envolvendo combinações de 4 números. Se precisar de combinações com mais ou menos números, será preciso adicionar ou remover laços e adaptar o código.

Para efetuar testes, sugiro que altere o intervalo para "A1:A10", por exemplo, para ter uma noção de quantas linhas são geradas e do funcionamento da rotina. No meu notebook, o processamento com 10 números levou menos de um minuto. A execução completa para todas as combinações de 62 números deve demorar muitas, muitas horas mesmo.

Pedro Martins


Formado em Tecnologia em Eletrônica Digital, já trabalhou como artefinalista, eletrotécnico, programador de CLP (para máquinas industriais) e analista de sistemas em sistema bancário, programando em COBOL.
Mexe com computadores e programação desde a segunda metade dos anos 1980, quando teve um MSX e aprendeu a programar em BASIC. É a favor da disseminação do conhecimento.

Autor Pedro Martins

Esta é uma breve descrição no bloco de autor sobre o autor. Você edita-lo, no html
«
Proxima
Postagem mais recente
»
Anterior
Postagem mais antiga

4 comentários

  1. Olá Sr Pedro Martins,
    Venho aqui para lhe dar feedback e agradecer toda a sua disponibilidade.
    Tentei hoje executar este código e verifiquei que o processamento dos dados é muito, mas mesmo muito mais rápido que no anterior.
    Penso que ao fim de cerca de 3/4 horas teríamos o processamento totalmente concluído.
    No entanto pára durante o processamento do bloco 7 e aparece uma janela com a seguinte mensagem: "O Excel não pode concluir esta tarefa com os recursos disponíveis. Seleccione menos dados ou feche outras aplicações." Clico em OK e aparece uma nova janela com esta mensagem: "Microsoft Visual Basic - Erro 400."
    Será que o Excel não tem capacidade suficiente? São muitas combinações!
    Será que o meu computador não tem capacidade? (Intel Core i5 processor 430M e 4GB Memory).
    Não tinha mais nenhuma aplicação aberta.
    Muito obrigada mais uma vez.

    ResponderExcluir
  2. Bom dia, Eliana. Realmente são muitas combinações e o processamento é muito demorado. É possível que o Excel não tenha conseguido terminar a tarefa devido à quantidade de memória disponível.

    Dividindo os 62 números em blocos de 4 cada, teríamos 15 blocos e meio para processar. Se parou no bloco 7, não chegou nem na metade do processo. Provavelmente seriam necessários 10 ou mesmo 12 GB de memória para conseguir processar todas as combinações.

    É bem provável que o Excel mantenha os dados na memória enquanto está processando a rotina. Por isso acredito que fazer uma alteração para processar as combinações faltantes não ajude.

    Há duas alternativas:
    - Criar 3 planilhas dessas, sendo uma processando o primeiro laço (Laco1) de 1 a 20, a segunda planilha processando de 21 a 40 e a terceira planilha processando de 41 a 62. Depois disso tentar juntar os dados em uma única planilha, embora acredito que o Excel vai pedir arrego de novo, por causa da memória. De qualquer forma, as combinações estariam à mão.
    - Criar um programa simples, que gere as combinações e grave em um arquivo, cada combinação gerando um registro no arquivo. Deve executar bem mais rápido e gerar todas as combinações possíveis. Pode usar a mesma lógica usada aqui, sem a necessidade de separar em blocos. Existem diversas linguagens que podem ser utilizadas para isso, sendo o VB.Net o mais próximo do VBA para quem quiser experimentar.

    Eu até iria fazer um programa dessas combinações como exercício, mas amanhã viajo e vou passar um mês fora.

    ResponderExcluir
    Respostas
    1. Muito obrigada pelas suas explicações, pelas suas partilhas e sobretudo pela sua ajuda. Não só explicou todos os passos como fez a "papinha" toda! Não esperava por tanto! Existem muito poucos assim. Um grande bem haja para si. Faça uma boa viagem.

      Excluir
  3. Olá Pedro ! Excelente abordagem... Tomei a liberdade do tema e vou postar neste domingo (19/06) e no próximo (26/06) vídeo-aulas sobre a geração de números e grupos de números no VBA. Vou utilizar outra abordagem... Obrigado mais uma vez por seus excelentes artigos!!!!

    ResponderExcluir