none
Remover linhas duplicadas, mas verificando só algumas colunas, de um List<string> RRS feed

  • Pergunta

  • Pessoal, tenho uma list<string> com milhares de linhas, cada linha e tabulada (tab).

    E cada tabulacao correspode a uma coluna (estou carregando isso de um .CSV.

    Ok, tudo bem até aqui, mas preciso fazer o seguinte procedimento:

    Preciso remover dessa lista todas as linhas com a coluna index 1, 5 e 12 iguais de outra linha.

    Ex. Coluna 1, 5 e 12 da linha um.... se tiver qualquer outra linha com os mesmos valores dessas colunas, deleta da lista.

    Eu cheguei aqui ao seguinte código:

                            List<int> indicesDuplicados = new List<int>();
                            for (int i = 0; i < linhasValidas.Count; i++)
                            {
                                string[] valoresTabulados_1 = linhasValidas[i].Split('\t');
                                string valor_1_1 = valoresTabulados_1[colunaCampaign];
                                string valor_1_2 = valoresTabulados_1[colunaLink];
                                string valor_1_3 = valoresTabulados_1[colunaAdGroup];
    
                                for (int k = 0; k < linhasValidas.Count; k++)
                                {
                                    if (k <= i)
                                        continue;
    
                                    string[] valoresTabulados_2 = linhasValidas[k].Split('\t');
                                    string valor_2_1 = valoresTabulados_2[colunaCampaign];
                                    string valor_2_2 = valoresTabulados_2[colunaLink];
                                    string valor_2_3 = valoresTabulados_2[colunaAdGroup];
                                    if (valor_1_1 == valor_2_1 && valor_1_2 == valor_2_2 && valor_1_3 == valor_2_3)
                                    {
                                        if (!indicesDuplicados.Contains(k))
                                        {
                                            indicesDuplicados.Add(k);
    
                                            await Task.Factory.StartNew(() =>
                                            {
                                                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
                                                {
                                                    _busyTitulo.Text = titulo;
                                                    Decimal calculo = Math.Round(((decimal)i / linhasValidas.Count) * 100, 0);
                                                    StringBuilder sb = new StringBuilder();
                                                    sb.Append(calculo);
                                                    sb.Append("% - ");
                                                    sb.Append(indicesDuplicados.Count);
                                                    sb.Append(" ocorrência(s).");
                                                    _busyContent.Text = sb.ToString();
                                                }));
                                            });
                                        }
                                    }
                                }
                            }
    
                            foreach (var item in indicesDuplicados)
                            {
                                linhasValidas[item] = "";
                            }

    Esse código funciona perfeitamente, só que tem um problema serio de performance.... tenho um CSV com 60.000 linhas.... se eu usar esse código ele irá fazer 3 bilhoes de iterações pra remover isso.

    O programa trava!

    Alguem conhece uma forma melhor?


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    sábado, 27 de abril de 2013 18:08

Respostas

  • Ok, mas ao menos as colunas chaves serão fixas, correto?

    Segue abaixo exemplo simulando o seu CSV:

    string[] csv = { 
        "valor_1|valor_2|valor_3|valor_4",
        "valor_1|valor_4|valor_3|valor_4",
        "valor_1|valor_3|valor_3|valor_4",
        "valor_1|valor_4|valor_3|valor_4",
        "valor_1|valor_3|valor_3|valor_4",
        "valor_1|valor_5|valor_3|valor_4"
                    };
    
    StringDictionary dict = new StringDictionary();
    StringBuilder chave = new StringBuilder();
    
    foreach (string linha_csv in csv)
    {
        string[] colunas_linhas = linha_csv.Split('|');
    
        chave.Append(colunas_linhas[0] + colunas_linhas[1]);
    
        if (!dict.ContainsKey(chave.ToString()))
        {
            dict.Add(chave.ToString(), linha_csv);
        }
    
        chave.Length = 0;
    }

    No caso, o vetor que declarei seria o seu arquivo csv. Para cada linha dele, ele recupera as colunas que são chaves (no caso utilizei a coluna 0 e 1) e verifica no dicionário se a chave existe. Caso não exista, adiciona a chave e a linha no dicionário.

    Obs: utilizei StringBuilder para caso existam 60.000 iterações, não seja criadas 60.000 strings em memória.

    Obs2: usei o split, porém, ele irá criar um vetor a cada interação. Um segundo passo seria verificar se existe uma maneira mais performática de recuperar as colunas.


    • Marcado como Resposta Tianodraco quinta-feira, 2 de maio de 2013 17:17
    sábado, 27 de abril de 2013 20:16
  • Olá Tiano,

    A variavel total era pra receber a qtde de linhas contidas na coleção linhas.

    Pois não tenho certeza se acessar o linhas.count é tão eficaz quanto acessar uma variavel int na memória.

    por desencargo, faça assim...

    1) atribua a variavel total para a qtde de linhas que tem na sua coleção 'linhas' (ANTES DO FOR);

    int total = linhas.count;

    2) Altere o código para o seguinte:

    decimal calculoNovo = Math.Round(((decimal)(total - i) / total) * 100, 0);
    if (calculo != calculoNovo)
    {
     calculo = calculoNovo;
     _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);
    }

    Estamos usando o IF, pois você só precisa exibir o progresso quando a % mudar.

    Fazendo isso se poupa a execução de algumas linhas de código.

    Abraço


    (Se a isto ajudou a resolver o problema, por favor, marque como resposta)

    • Marcado como Resposta Tianodraco quinta-feira, 2 de maio de 2013 17:17
    quinta-feira, 2 de maio de 2013 12:54
  • Olá Tiano,

    A idéia do if é pra economizar a execução dos comandos:

    _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);

    só é necessário exibir, ou rodar estes comandos se a % é alterada. por isso o if...

    PS : Só faz sentido usar o if se você está focando seu código para melhor performance.

    Um exemplo do porque usar vai abaixo:

    Se você tiver 1000 itens na lista, 1% do progresso significará processar 10 itens... e se todos os itens da lista estiverem OK, sua app vai processar 10 vezes o código abaixo...

    _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("1% - 0 ocorrencia(s).", calculo, indicesInvalidos);

    Assim sendo, usar um if economizaria 9*3 (27) linhas processadas. (durante o processamento destes 10 primeiros itens.

    Entendeu?

    Abraço!!!


    (Se a isto ajudou a resolver o problema, por favor, marque como resposta)


    quinta-feira, 2 de maio de 2013 16:01

Todas as Respostas

  • Olá,

    Eu tentaria fazer o seguinte:

    - Criaria uma classe com uma propriedade para cada coluna tabulada;

    - No construtor dessa classe, você passaria a string e trataria para cada tabulação setar a respectiva propriedade;

    - Nesta classe sobrescreveria o método Equal (http://msdn.microsoft.com/en-us/library/336aedhh(v=vs.71).aspx), verificando as propriedades (colunas) que determinarão que as linhas serão iguais;

    - Criaria uma Lista dessa classe;

    - Para cada iteração das linhas lidas no csv, criaria uma instância da classe, verificaria se a Lista contém o objeto e só adicionaria caso não contivesse;

    - Se precisar retornar a linha no formato tabulado, sobrescreveria o método ToString() e trataria para retornar a string tabulada (http://msdn.microsoft.com/pt-br/library/ms173154(v=vs.80).aspx).

    sábado, 27 de abril de 2013 19:07
  • Outra forma seria criar um Dictionary, cuja a chave seria as colunas que não podem se repetir e o valor a linha completa.

    O fluxo seguiria conforme exemplo anterior, porém, ao invés de instanciar a classe e verificar na lista, você verificaria no Dictionary se já existe a chave conforme linha atual do csv, adicionando somente no caso de não haver.


    sábado, 27 de abril de 2013 19:14
  • Diego obrigado ela resposta.

    Eu nao posso criar a classe pq eu nao sei quantas colunas o CSV pode ter, isso e aleatorio, pode ter 15 ou pode ter 50+...

    Vc oderia mostrar um exemplo com Dictionary?


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    sábado, 27 de abril de 2013 19:24
  • Ok, mas ao menos as colunas chaves serão fixas, correto?

    Segue abaixo exemplo simulando o seu CSV:

    string[] csv = { 
        "valor_1|valor_2|valor_3|valor_4",
        "valor_1|valor_4|valor_3|valor_4",
        "valor_1|valor_3|valor_3|valor_4",
        "valor_1|valor_4|valor_3|valor_4",
        "valor_1|valor_3|valor_3|valor_4",
        "valor_1|valor_5|valor_3|valor_4"
                    };
    
    StringDictionary dict = new StringDictionary();
    StringBuilder chave = new StringBuilder();
    
    foreach (string linha_csv in csv)
    {
        string[] colunas_linhas = linha_csv.Split('|');
    
        chave.Append(colunas_linhas[0] + colunas_linhas[1]);
    
        if (!dict.ContainsKey(chave.ToString()))
        {
            dict.Add(chave.ToString(), linha_csv);
        }
    
        chave.Length = 0;
    }

    No caso, o vetor que declarei seria o seu arquivo csv. Para cada linha dele, ele recupera as colunas que são chaves (no caso utilizei a coluna 0 e 1) e verifica no dicionário se a chave existe. Caso não exista, adiciona a chave e a linha no dicionário.

    Obs: utilizei StringBuilder para caso existam 60.000 iterações, não seja criadas 60.000 strings em memória.

    Obs2: usei o split, porém, ele irá criar um vetor a cada interação. Um segundo passo seria verificar se existe uma maneira mais performática de recuperar as colunas.


    • Marcado como Resposta Tianodraco quinta-feira, 2 de maio de 2013 17:17
    sábado, 27 de abril de 2013 20:16
  • Diego vou passar a noite testando seu codigo... vamos ver aqui...

    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    sábado, 27 de abril de 2013 21:24
  • Blz, espero que te ajude!

    Caso contrário, segunda estou de volta!

    Abraços e boa sorte com a implementação.

    Obs: não esqueça de fazer um backup antes de alterá-la inteira.

    sábado, 27 de abril de 2013 21:28
  • Olá Tianodraco,

    Conseguiu resolver seu problema com a sugestao do Diego? Se sim, poste pra gente ver como ficou e também para ajudar outras pessoas que passem por essa mesma necessidade no futuro...

    Se nao conseguiu solucionar, indique aqui pra gente até que ponto você chegou... Uma outra ideia seria talvez utilizar uma LINQ query agrupando os dados pelos índices que você quiser... Dessa forma você conseguiria verificar quais "chaves" têm valores repetidos...


    André Alves de Lima
    Microsoft MVP - Client App Dev
    Visite o meu site: http://www.andrealveslima.com.br
    Me siga no Twitter: @andrealveslima

    segunda-feira, 29 de abril de 2013 10:55
    Moderador
  • Opa pessoal, bom dia, consegui sim resolver graças ao post do Diego.

    A adaptação que eu coloquei posto aqui agora:

    for (int i = linhas.Count - 1; i >= 0; i--)
                                            {
                                                valoresTabulados_1 = linhas[i].Split('\t');
                                                valor_1 = valoresTabulados_1[colunaCampaign];
                                                valor_2 = valoresTabulados_1[colunaLink];
                                                valor_3 = valoresTabulados_1[colunaAdGroup];
    
                                                if (string.IsNullOrEmpty(valor_2.Trim()) || string.IsNullOrEmpty(linhas[i].Trim()))
                                                {
                                                    indicesInvalidos++;
                                                    linhas.RemoveAt(i);
                                                    continue;
                                                }
    
                                                chave.Append(string.Concat(valor_1, valor_2, valor_3));
    
                                                if (dic.ContainsKey(chave.ToString()))
                                                {
                                                    linhas.RemoveAt(i);
                                                    //await Task.Factory.StartNew(() =>
                                                    //{
                                                    indicesInvalidos++;
    
                                                    _busy.IsBusy = true;
                                                    _busyTitulo.Text = titulo;
                                                    Decimal calculo = Math.Round(((decimal)i / linhas.Count) * 100, 0);
                                                    StringBuilder sb = new StringBuilder();
                                                    sb.Append(calculo);
                                                    sb.Append("% - ");
                                                    sb.Append(indicesInvalidos);
                                                    sb.Append(" ocorrência(s).");
                                                    _busyContent.Text = sb.ToString();
    
                                                    //});
                                                }
                                                else
                                                {
                                                    dic.Add(chave.ToString(), linhas[i]);
                                                }
    
                                                chave.Length = 0;
                                            }

    Criei dentro de um loop invertido e fui removendo todas as linhas que não estão dentro do dictionary... simples, rápido e eficaz.... de 3 bilhoes e 600 milhoes de iterações, caiu para 60.000 com esse código.

    :)


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    terça-feira, 30 de abril de 2013 12:20
  • Tiano,

    Fiz um código que poderia ajudar na performance... Usando HashTable (http://blog.bodurov.com/Performance-SortedList-SortedDictionary-Dictionary-Hashtable/)

    string[] valoresTabulados_1; string valor_1 = string.Empty; string valor_2 = string.Empty; string valor_3 = string.Empty; int colunaCampaign = 1; int colunaLink = 2; int colunaAdGroup = 3; int indicesInvalidos = 0; bool linhaOK = false; string chave = string.Empty; List<string> ArquivoSujo = new List<string>(); Hashtable ArquivoLimpo = new Hashtable();

    int total=0; for (int i = ArquivoSujo.Count - 1; i >= 0; i--) { linhaOK = false; string linha = ArquivoSujo[i]; valoresTabulados_1 = linha.Split('\t'); if (!string.IsNullOrEmpty(linha)) { // Se existe linha, então extrai as colunas "chave" valor_1 = valoresTabulados_1[colunaCampaign]; valor_2 = valoresTabulados_1[colunaLink]; valor_3 = valoresTabulados_1[colunaAdGroup]; if (!string.IsNullOrEmpty(valor_2.Trim())) { chave = string.Concat(valor_1, valor_2, valor_3); if (ArquivoLimpo.ContainsKey(chave)) linhaOK = false; } if (!linhaOK) indicesInvalidos++; else ArquivoLimpo.Add(chave,linha); if (calculo != Math.Round(((decimal)i / total) * 100, 0)) { calculo = Math.Round(((decimal)i / total) * 100, 0); _busy.IsBusy = true; _busyTitulo.Text = titulo; _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos); } } }


    Abraço a todos!!!

    (Se a isto ajudou a resolver o problema, por favor, marque como resposta)



    terça-feira, 30 de abril de 2013 13:51
  • Tiano,

    O código postado pelo Fabio Alves deverá ter uma performance melhor.

    Sugeri o uso do Dicionário imaginando que no final você iria percorrer as linhas não repetidas, por isso a utilização de uma estrutura com chave/valor.

    Também indiquei a solução, pois imaginava que essa verificação seria feita no carregamento do arquivo, mas se você já tem uma em memória, não faria sentido utilizar o dicionário.

    Por isso vale fazer um teste com a solução do Fabio.

    Outra dica: se você conseguisse fazer a verificação de duplicidade no carregamento da primeira lista (sem carregar na lista os itens duplicados), não precisaria remover desta depois e a performance e a utilização de memória seria melhor.

    terça-feira, 30 de abril de 2013 14:54
  • Obrigado Diego e Fabio pels respostas.... vou testar o codigo do Fabio e posto aqui um feedback... 

    Uma coisa q preciso dizer é q logo depois tenho q passar essa List<string> para um método... fazendo com hashtable, como converteria depois para List<string>... e sera q convertendo a carga não ficaria maior?

    Aproveitando q estamos falando de performance, qual seria o melhor jeito de medir o tempo de um procedimento? (fazer e comparar performance de algoritimo)?


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.


    • Editado Tianodraco quarta-feira, 1 de maio de 2013 21:39
    quarta-feira, 1 de maio de 2013 21:34
  • Tiano,

    Para converter em Lista os Valores das linhas tanto do Dictionary quanto do HashTable, basta você fazer o seguinte:

    ArquivoLimpo.Values.Cast<string>().ToList();

    Quanto a medir a performance, é sempre bem complicado ter uma medida precisa. Tem muitas formas de medir, incluindo utilizar o performance counter do windows. Era bom você dar uma pesquisada nas opções e verificar qual é a mais viável para o seu caso.

    quarta-feira, 1 de maio de 2013 22:38
  • Opa, blz Diego !

    Vou colocoar os créditos de vcs 2 no meu código.

    Pessoal, está dando erro na seguinte linha:

    calculo != Math.Round(((decimal)i / total) * 100, 0) - Divisão por zero.

    Onde a variável "total" é incrementada?

    Aproveitando, me explica esse código:

    if (calculo != Math.Round(((decimal)i / total) * 100, 0))
     {
       calculo = Math.Round(((decimal)i / total) * 100, 0);

       ...


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.


    • Editado Tianodraco quinta-feira, 2 de maio de 2013 12:00
    quinta-feira, 2 de maio de 2013 11:58
  • Tiano,

    O erro ocorre porque a variável total esta com o valor zero e não é possível dividir por zero.

    terá que fazer uma validação para não fazer o calculo se o valor de total = 0.

    Quanto ao código... ele está dividindo o valor da iteração pelo total, multiplicando por sem e arredondando para 0 casas decimais. Parece que está querendo fazer algum tipo de porcentagem, considerando a iteração e o total. Mas se o comportamento esperado é esse eu não tenho como dizer, tem que verificar as regras de negócio.

    quinta-feira, 2 de maio de 2013 12:28
  • Diego, eu adaptei o código e cheguei a isto:

    await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
                            {
                                for (int i = linhas.Count - 1; i >= 0; i--)
                                {
                                    string linha = linhas[i];
                                    valoresTabulados_1 = linhas[i].Split('\t');

                                    if (!string.IsNullOrEmpty(linha.Trim()))
                                    {
                                        //Se existe linha, então extrai as clunas chaves
                                        valor_1 = valoresTabulados_1[colunaCampaign];
                                        valor_2 = valoresTabulados_1[colunaLink];
                                        valor_3 = valoresTabulados_1[colunaAdGroup];

                                        if (!string.IsNullOrEmpty(valor_2.Trim()))
                                        {
                                            chave = string.Concat(valor_1, valor_2, valor_3);
                                            
                                            if (linhasValidas.ContainsKey(chave))
                                                indicesInvalidos++;
                                            else
                                                linhasValidas.Add(chave, linha);

                                            if (calculo != Math.Round(((decimal)(linhas.Count - i) / linhas.Count) * 100, 0))
                                            {
                                                calculo = Math.Round(((decimal)(linhas.Count - i) / linhas.Count) * 100, 0);
                                                _busy.IsBusy = true;
                                                _busyTitulo.Text = titulo;
                                                _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);
                                            }
                                        }
                                    }
                                }
                            }));

    O cálculo do percentual estava invertido (indo de 100% a 0%), por isso o refiz.

    Algumas considerações minhas:

    1 - Não vi necessidade de criar uma variável booleana apenas pra fazer uma verificação, impementei direto no if

    2 - O dispatcher tirei do loop

    3 - Não vi a necessidade da variável "total", fiz direto.

    Quanto a duvida anterior eu me referia em pq ele precisa verificar se o valor de calculo é igual a um percentual especifico, para só então configurar a variável calculo para o valor corrente no loop. Ao meu ver, essa verificação "if" sempre ocorrerá, até o final do loop. Não seria melhor economizar o processamento extra simplesmente fazendo:

    calculo = Math.Round(((decimal)(linhas.Count - i) / linhas.Count) * 100, 0);
     _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
    _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);


    ao invés de:

     if (calculo != Math.Round(((decimal)(linhas.Count - i) / linhas.Count) * 100, 0))
      {
           calculo = Math.Round(((decimal)(linhas.Count - i) / linhas.Count) * 100, 0);
           _busy.IsBusy = true;
           _busyTitulo.Text = titulo;
          _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);
     }

    Que vc cha?


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.


    • Editado Tianodraco quinta-feira, 2 de maio de 2013 12:48
    quinta-feira, 2 de maio de 2013 12:44
  • Olá Tiano,

    A variavel total era pra receber a qtde de linhas contidas na coleção linhas.

    Pois não tenho certeza se acessar o linhas.count é tão eficaz quanto acessar uma variavel int na memória.

    por desencargo, faça assim...

    1) atribua a variavel total para a qtde de linhas que tem na sua coleção 'linhas' (ANTES DO FOR);

    int total = linhas.count;

    2) Altere o código para o seguinte:

    decimal calculoNovo = Math.Round(((decimal)(total - i) / total) * 100, 0);
    if (calculo != calculoNovo)
    {
     calculo = calculoNovo;
     _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);
    }

    Estamos usando o IF, pois você só precisa exibir o progresso quando a % mudar.

    Fazendo isso se poupa a execução de algumas linhas de código.

    Abraço


    (Se a isto ajudou a resolver o problema, por favor, marque como resposta)

    • Marcado como Resposta Tianodraco quinta-feira, 2 de maio de 2013 17:17
    quinta-feira, 2 de maio de 2013 12:54
  • Tiano,

    É difícil dar opinião a esse respeito, pois, como falei, não conheço as regras de negócio e o que está sendo esperado com esse cálculo.

    Quanto ao IF (se sempre for entrar nele, realmente não teria necessidade).

    quinta-feira, 2 de maio de 2013 13:34
  • Blz Fabio, fiz as alteracoes q vc falou, mas so não entendi ainda a questão do if.... 

    Me parece q o valor de calculo e calculo novo só são iguais no meio do loop, já q calculo comeca com 0 e calculo novo com o tamanho da lista... mesmo assim só serão iguais se o total da lista forpar... aí eles vao se encontrar no meio da lista.... se for impar eles nunc se encontrarão...

    Suponhamos q a lista tenha 100 linhas... quando calculo for 1, calculonovo sera 99.... quando calculo for 49, calculo novo sera 51... aí eles vao se encontrar no 50!.... quando se encontrarem no 50, o q tiver dentro do if nao sera processado, assim, a contagem pulará de 49% para 51% na proxima iteracao....

    Não sei se estou pensando direito, rss.....


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    quinta-feira, 2 de maio de 2013 14:24
  • Olá Tiano,

    A idéia do if é pra economizar a execução dos comandos:

    _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("{0}% - {1} ocorrencia(s).", calculo, indicesInvalidos);

    só é necessário exibir, ou rodar estes comandos se a % é alterada. por isso o if...

    PS : Só faz sentido usar o if se você está focando seu código para melhor performance.

    Um exemplo do porque usar vai abaixo:

    Se você tiver 1000 itens na lista, 1% do progresso significará processar 10 itens... e se todos os itens da lista estiverem OK, sua app vai processar 10 vezes o código abaixo...

    _busy.IsBusy = true;
     _busyTitulo.Text = titulo;
     _busyContent.Text = string.Format("1% - 0 ocorrencia(s).", calculo, indicesInvalidos);

    Assim sendo, usar um if economizaria 9*3 (27) linhas processadas. (durante o processamento destes 10 primeiros itens.

    Entendeu?

    Abraço!!!


    (Se a isto ajudou a resolver o problema, por favor, marque como resposta)


    quinta-feira, 2 de maio de 2013 16:01
  • Blz, clareou sim, agora entendi.

    Como o arredondamento não permite as casas decimais, os valores q  são processados dentro de um ponto percentual não precisam ser analisados....

    Perfeito e muito inteligente!

    Obrigado a vcs pela paciência, a ajuda de vcs foi essencial e os créditos estarão garantidos no código... até a próxima, amigos.


    Se a resposta foi útil, por favor marque como útil. Leia a bíblia.

    quinta-feira, 2 de maio de 2013 17:17