Ir ao conteúdo
  • Cadastre-se

C Conta quantidade de Nomes num arquivo.txt


nigolino

Posts recomendados

#include<stdio.h>
#include<ctype.h>
#include<string.h>

//*PROGRAMA - no qual o usuário informa o nome do arquivo e o nome de uma pessoa, e retorne o número de vezes que aquela pessoa aparece no arquivo*/
void lerArqTxt(const char *palavra) {
   char caracter;
   int loop_var, contador;
   bool flag;
   FILE *arq;

   arq=fopen("arquivo1.txt","r");
       if(!arq)
           fclose(arq);
       else
           while(!feof(arq)) {
               flag = false;

               for(loop_var = 0; loop_var < sizeof(palavra) / sizeof(palavra[0]); 
               ++loop_var) {
                   caracter = getc(arq);

                   if(caracter != palavra[loop_var]) {
                       flag = true;
                       break;
                   }
               }

               if(flag == false)
                   ++contador;
           }
   fclose(arq);
   printf("\n\n");
}

int main()
{
   char palavra[50];
   fgets(palavra, 49, stdin);

   lerArqTxt(palavra); // Passaria o nome por parâmetro para a função.

   return (0);

}

Pessoal esse arquivo quando funcionar vai ajudar no meu trabalho diário. Tenho um arquivo .txt com 45.000 pessoas e preciso contar os nomes repetidos num arquivo (arquivoum.txt)

Link para o comentário
Compartilhar em outros sites

o arquivo está neste formato: números, letras, números...

3401 LUCIA GONCALVES  0017786001 00000        BRL000000000000000000000000016182                            00000

3402 MARTA PEREIRA DOS SANTOS 0017786002 00000        BRL000000000000000000000000010283                          00000

adicionado 0 minutos depois

o arquivo está neste formato: números, letras, números...

3401 LUCIA GONCALVES  0017786001 00000        BRL000000000000000000000000016182                            00000

3402 MARTA PEREIRA DOS SANTOS 0017786002 00000        BRL000000000000000000000000010283                          00000

Link para o comentário
Compartilhar em outros sites

O ideal e ler linha por linha

 

void lerArqTxt(const char *palavra) {
   char caracter;
   char line[150];//acredito que 150 eh suficiente para ler uma linha
   int loop_var, contador;
   bool flag;
   FILE *arq;

   arq=fopen("arquivo1.txt","r");
       if(!arq)
           fclose(arq);
       else
           while((fgets(line,150, arq)!=NULL) {
               if (strstr(line, palavra) != NULL) ++contador;
           }
   fclose(arq);
   printf("\n\n%d", contador);
}
                                                                       
int main()
{
   char palavra[50];
   fgets(palavra, 50, stdin);
   palarava[strlen(palavra)-1]='\0';//remove o enter

   lerArqTxt(palavra); // Passaria o nome por parâmetro para a função.

   return (0);

}

Veja se assim funciona, não testei.

Link para o comentário
Compartilhar em outros sites

Olá!

 

Em geral compensa gastar um tempo vendo o formato interno do arquivo antes de escrever algo. Isso com alguma ferramenta como um editor hexadecimal. De todo modo, acha que pode assumir que:

  • o arquivo como você disse é do tipo texto então está formatado em linhas
  • o primeiro campo é numérico
  • o segundo campo é o nome e não tem números de jeito nenhum
  • o terceiro campo é outro numero
  • as linhas tem comprimento variável

Se for o caso, eis uma possível lógica:

  • pode abrir o arquivo como texto mesmo
  • le a linha inteira para a memoria
  • encontra o primeiro não-numero --- deve ser o inicio do nome
  • a partir dai encontra o primeiro numero --- deve ser o inicio do terceiro campo
  • nesse intervalo está o nome: então isola eventuais brancos a esquerda e a direita e assim delimita cada nome
  • por segurança converte tudo para maiúscula e troca os caracteres acentuados pelo caracter sem acento, tipo ó por o e ta
  • coloca isso numa estrutura de dados qualquer, tipo lista ligada ou árvore ou algo assim, para ficar em ordem já enquanto insere

Como tem essa estimativa de 45 mil nomes não é nada expressivo para os computadores de hoje em dia e pode ter isso em uns poucos megabytes apenas.

 

Com a estrutura carregada com os nomes aí você faz o que precisar. Estou imaginando que quer manter tudo isso em C estritamente. É o caso?

 

Quer ver um exemplo? 

 

 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

Em 23/08/2019 às 20:41, arfneto disse:

Olá!

 

Em geral compensa gastar um tempo vendo o formato interno do arquivo antes de escrever algo. Isso com alguma ferramenta como um editor hexadecimal. De todo modo, acha que pode assumir que:

  • o arquivo como você disse é do tipo texto então está formatado em linhas
  • o primeiro campo é numérico
  • o segundo campo é o nome e não tem números de jeito nenhum
  • o terceiro campo é outro numero
  • as linhas tem comprimento variável

Se for o caso, eis uma possível lógica:

  • pode abrir o arquivo como texto mesmo
  • le a linha inteira para a memoria
  • encontra o primeiro não-numero --- deve ser o inicio do nome
  • a partir dai encontra o primeiro numero --- deve ser o inicio do terceiro campo
  • nesse intervalo está o nome: então isola eventuais brancos a esquerda e a direita e assim delimita cada nome
  • por segurança converte tudo para maiúscula e troca os caracteres acentuados pelo caracter sem acento, tipo ó por o e ta
  • coloca isso numa estrutura de dados qualquer, tipo lista ligada ou árvore ou algo assim, para ficar em ordem já enquanto insere

Como tem essa estimativa de 45 mil nomes não é nada expressivo para os computadores de hoje em dia e pode ter isso em uns poucos megabytes apenas.

 

Com a estrutura carregada com os nomes aí você faz o que precisar. Estou imaginando que quer manter tudo isso em C estritamente. É o caso?

 

Quer ver um exemplo? 

 

 

Sim eu pretendo manter tudo em C. Pode mostrar um exemplo?

Link para o comentário
Compartilhar em outros sites

exemplo.png.6c779bc79eca344e437739558431e52e.png

Em 24/08/2019 às 22:14, nigolino disse:

Sim eu pretendo manter tudo em C. Pode mostrar um exemplo?

 

Entendo. Pergunto porque talvez você fosse o caso de usar um banco de dados se precisar fazer consultas mais elaboradas no futuro. sqlite é a opção simples. E se pudesse usar outra linguagem, C++ tem várias estruturas que poderia aproveitar para indexar tudo, como listas, filas, árvores e coisas assim. Só declarar e usar.

Sobre SQLite em C pode ver algo em https://www.sqlite.org/cintro.html.

 

Claro, em C é mais divertido :D

 

De volta ao tópico: Em relação ao seu exemplo:

 

exemplo.png.6c779bc79eca344e437739558431e52e.png

 

Acho mais produtivo usar então os argumentos direto na linha de comando, e vou deixar um exemplo com dois parâmetros, para usar assim --- imaginando que seu programa se chame cadastro:

cadastro arquivo linhas 

ou apenas 

cadastro

E se você chamar assim

cadastro arquivo.txt 200    le as primeiras 200 linhas do arquivo "arquivo.txt"
cadastro arquivo.doc        le todas as linhas de "arquivo.doc"
cadastro                    le os campos a partir da entrada padrão mesmo

Assim fica mais fácil de testar... E você pode editar direto uns arquivos de teste

 

Seguindo a lógica de que falei, escrevi uma parte de uma possível solução em puro e simples C. Vou postar aqui agora porque já pode ser útil. Falta implementar a estrutura de dados para acumular os valores e tenho uma ideia para uma solução simples que permitiria responder sua questão. Quando eu tiver um tempo volto a postar a solução, e aí completa. Se eu tiver tempo talvez escreva amanhã.

 

Eis o que tem aqui:

 

O programa lê o arquivo de entrada e extrai os dados, com a opção de limitar o número de linhas... Para essas 11 linhas em "c.txt"

2345678 nome  com    muitos      espacos   12345678
12345678 nome com espacos   12345678

ABCD
3401 LUCIA GONCALVES  0017786001 00000        BR00      00000


3401 LUCIA  GONCALVES  0017786001 00                       00000
3402    MARTA       PEREIRA DOS SANTOS 00177   00000
3401     LUCIA GONCALVES  0017786001 00000      00000
3402 MARTA PEREIRA DOS SANTOS 0017786002 00000 08

E usando cadastro c.txt 8

 

O programa gera

run-test.thumb.png.e1248285ae4770bc6a974b96ce79ff31.png

 

 

Como as linhas aparecem na tela você pode ir ao arquivo texto de origem e corrigir algo ou ver o que está errado no arquivo ou no programa e continuar refinando a solução. Ou se usa linux pode ir usando grep awk sort,  pr e ir criando seus relatórios sem programar nada.

 

Para ir a uma determinada linha do arquivo de entrada e que aparece na tela você pode, se usa linux e vi, digitar :n e ir para a linha n do arquivo direto. Se usa Visual Studio pode usar control-G e digitar a linha.

Vendo uma linha com mais detalhe:

linha.png.b1a5392ee7ad46860459c947d7f0e974.png

 

Eu uso Windows 10 e Visual Studio '19 e meu compilador não é assim o máximo para rodar C, e faz isso como uma cortesia :D digamos. Até tenho máquinas Linux na mesa de trás mas não tenho tempo para criar um ambiente nelas para essas coisas que faço com meu tempo livre...

 

De volta ao programa:

note o nome do programa na primeira linha: trata-se do argumento zero da linha de comando e sempre tem o nome do programa executável: argv[0]. Os próximos valores são opcionais e são aqueles que você digita na lionha de comando e vai entender ao ler a fonte em main().

Ai vem para cada linha da entrada o numero dela, o tamanho do nome e as posições de início e fim como o programa identificou. Logo depois uma linha mostra com asteriscos onde o programa acha que está o nome, para facilitar para conferir.

Depois o programa isola o nome, descartando o que não for letra à esquerda e à direita e depois troca qualquer sequência de espaços por um espaço apenas, para poder tratar o caso em que um mesmo nome tenha sido digitado com espaços a mais ou a menos. E converte todas as letras para minúsculas para ter certeza que não vai diferenciar nomes só por uma inicial maiúscula por exemplo. Essa última linha, que tem a tag (comprimido) é a que será usada para cadastrar o nome no banco de dados.

 

O programa tem essas 5 funções, alem de main()

int acha_o_nome(int, int, char*);
int ajusta_o_nome(int, char*);
int completa_buffer(Buffer*);
int trata_o_nome(int, char*);
int uma_linha(char*, const int, Buffer*);

e a lógica é bem simples. Não é a melhor solução, nem uma boa solução possivelmente, mas sugiro dar uma lida. Gira em torno da estrutura abaixo e gera os nomes para cadastrar depois e comparar. Na prática lê o arquivo em segmentos de um certo amanho --- como 32k no exemplo --- e aí vai caçando as linhas dentro do buffer. Quando acaba lê mais um pedaço e assim vai.

typedef struct
{
    unsigned char *        pBuffer;
    int                    disponiveis;
    int                    proximo;
    FILE *                 arquivo;
}
Buffer;

Eis o trecho de main() que implementa a lógica

 


 

    buffer.pBuffer        = malloc((size_t)(_TAMANHO_BUFFER));
    buffer.disponiveis    = 0;
    buffer.proximo        = 0;
    buffer.arquivo        = Entrada;

    do
    {
        int t;
        status = uma_linha(linha, _LIMITE_LINHA, &buffer);
        if (status > 0)
        {    // leu uma linha: em branco?
            linhas_lidas++;
            if ((t = strlen(linha)) > 0)
            {    // tem algo na linha
                acha_o_nome(linhas_lidas, t, linha);
            }
            else
            {
                linhas_em_branco++;
                fprintf(stderr, "Linha %d: Linha em branco\n", linhas_lidas);
            }    // end if

            if (linhas_lidas < limite_teste) continue;
            fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
            break;
        }    // end if
    } while (status >=0);

    free(buffer.pBuffer);
    fclose(Entrada);
    printf("Final: Lidas %d linhas --- %d em branco\n", linhas_lidas, linhas_em_branco);
    return EXIT_SUCCESS;
  }

Na verdade é quase o programa todo :D 

O programa chama uma_linha() para tentar ler a próxima linha e se consegue chama acha_o_nome() para isolar o nome e esta chama trata_o_nome() para continuar o serviço. Se já tem um plano para cadastrar os caras pode usar direto em ajusta_o_nome() que é onde o programa faz a compressão final do nome lido.

 

Depois escrevo uma opção para o cadastro e a identificação final dos nomes e com as duplicatas identificadas.

 

Eis o programa todo até aqui

#define    _CRT_SECURE_NO_WARNINGS
#define    _TAMANHO_BUFFER  (32768)
#define    _LIMITE_LINHA    (128)

#include "ctype.h"
#include "errno.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct
{
    unsigned char *        pBuffer;
    int                    disponiveis;
    int                    proximo;
    FILE *                arquivo;
}
Buffer;

typedef struct
{
    unsigned int        linhas_lidas;
    unsigned int        linhas_em_branco;
    unsigned int        nomes_validos;
}
Base_de_dados;

int acha_o_nome(int, int, char*);
int ajusta_o_nome(int, char*);
int completa_buffer(Buffer*);
int trata_o_nome(int, char*);
int uma_linha(char*, const int, Buffer*);

int acha_o_nome(int n, int t, char* l)
{
    // n = numero da linha
    // t = tamanho da linha
    // l = a linha
    int inicio = 0;
    int final = 0;
    int i;
    // linha em l com t caracteres, t>0
    for (inicio = 0; inicio < t; inicio++) if (!isdigit(l[inicio])) break;
    for (final = inicio; final < t; final++) if (isdigit(l[final])) break;
    if (inicio == final)
    {
        fprintf(stderr, "Linha %d: Nome nao identificado\n", n);
        return 1;
    }
    fprintf(stderr,"Linha %d: Nome com %d caracteres. Posicao [%d,%d]\n", n, (final - inicio), inicio, final);
    fprintf(stderr,"%s\n", l);
    for (i = 0; i < inicio; i++) fprintf(stderr, "-");
    for (i = inicio; i < final; i++) fprintf(stderr, "*");
    for (i = final; i < t; i++) fprintf(stderr, "-");
    fprintf(stderr, "\n");

    // cria uma string com o nome e passa para a rotina que vai 
    // cadastrar a entrada
    i = final - inicio + 1;
    char* pessoa = malloc((size_t) i);
    *(pessoa+i-1) = 0;    // pra nao esquecer: finaliza a string
    memcpy(pessoa, (l+inicio), (final-inicio) );
    fprintf(stderr, "[%s]\n", pessoa);

    trata_o_nome(n, pessoa);

    free(pessoa);
    return EXIT_SUCCESS;
}    // end acha_o_nome()


int ajusta_o_nome(int n, char* nome)
{
    // a partir de um nome ok converte caracteres e comprime espacos
    int        in_space = 0;        // usado para comprimir os espacos
    int        t = strlen(nome);
    char*    pVetor = NULL;        // ponteiro para a string de saida
    char*     vetor = malloc(t+1);

    // copia nome para vetor comprimindo os brancos ou tabs
    *vetor = tolower(*nome);    // copia o primeiro caracter
    pVetor = vetor + 1;
    *pVetor = 0;

    for (int i=1; i<t-1; i++)
    {
        if (isblank(nome[i]))
        {
            if(in_space==1)
            {
                continue;
            }
            else
            {
                in_space = 1;
                *pVetor = ' ';
                pVetor++;
                continue;
            }
        }
        else
        {
            in_space = 0;
            *pVetor = tolower(nome[i]);
            pVetor++;
        }    // end if
    }    // end for
    // copia o ultimo caracter, que com certeza nao era branco
    *pVetor = tolower(*(nome+t-1));
    pVetor++;
    *pVetor = 0;    // termina a string de saida
    fprintf(stderr, "Linha %d: Nome[%s] (comprimido)\n", n, vetor);
    free(vetor);
    return 0;
}    // end ajusta_o_nome()


int completa_buffer(Buffer* b)
{
    // retorna
    //  0 ao completar o buffer ou
    // -1 se EOF ou erro no arquivo
    unsigned char* p = b->pBuffer;
    // desloca para o inicio o que tinha sobrado no buffer
     for(int i=0; i<(b->disponiveis); i++)    *(p+i) = *(p+ i + b->proximo);
    int a_ler = _TAMANHO_BUFFER - b->disponiveis;    // tenta completar
    p = b->pBuffer + b->disponiveis;    // le a partir do que ja tinha
    int lidos = fread( p, 1, a_ler, b->arquivo );
    b->disponiveis = b->disponiveis + lidos;
    b->proximo = 0;
    if (lidos == 0)    return(-1);    else return 0;    // sinaliza final
}    // end completa_buffer()


int trata_o_nome(int n, char* nome)
{
    int t = strlen(nome);
    int inicio = 0;
    int final = 0;
    // linha em l com t caracteres, t>0
    for (inicio=0; inicio<t; inicio++)
    {
        if (isblank(nome[inicio]))
        {
            continue;
        }
        else
        {
            break;
        }    // end if
    }    //    end for
    
    if (inicio >= t)
    {    // pode estar toda em branco
        fprintf(stderr, "Linha %d: Nome [%s] em branco\n", n, nome);
        return -1;
    }

    for (final=(t-1); final>=inicio; final--)
    {
        if (!isblank(nome[final]))
        {
            break;
        }
        else
        {
            continue;
        }    // end if
    }    // end for
    if (inicio >= final) return 1;
    nome[final+1] = 0;    // trunca aqui
    fprintf(stderr, "Linha %d: Nome [%s]\n", n, nome + inicio);
    ajusta_o_nome(n, nome+inicio);
    fprintf(stderr, "__________ __________ __________ __________ __________ __________ \n\n");

    return 0;
}    // end trata_o_nome()


int uma_linha(    char* linha, const int maximo, Buffer* buf)
{
    //
    // retorna
    // - 1 e a linha em linha ou
    // - 0 se nao tem uma linha completa no buffer
    // - -1 se acabou o arquivo
    //
    int lidos;
    unsigned char* inicio = buf->pBuffer + buf->proximo;
    unsigned char* p = inicio;
    for (int i=0; i<buf->disponiveis; i++)
    {
        if (*p == '\n')
        {
            *p = 0;
            strcpy(linha, inicio);
            lidos = strlen(linha);
            buf->proximo += 1 + i;
            buf->disponiveis -= i+1;
            return 1;
        }
        else
        {
            p++;
        }    // end if
    }    // end for
    int n = completa_buffer(buf);
    return n;
}    // end uma_linha()

int main(int argc, char** argv)
{
    FILE*            Entrada = NULL;
    Buffer            buffer;
    Base_de_dados    base;
    int                linhas_lidas = 0;
    int                linhas_em_branco = 0;
    int                status = 0;
    char            linha[256];
    int                limite_teste;    // para em n linhas do arquivo

    printf("\n\nRodando: %s\n\n\n", argv[0]);
    if (argc > 1)
    {
        Entrada = fopen(argv[1], "r");
        if (Entrada == NULL)
        {
            fprintf(stderr, "Erro abrindo %s\n", argv[1]);
            return 0;
        }    // end if
        fprintf(stderr, " - Lendo a partir do arquivo %s\n", argv[1]);
        if (argc > 2)
        {
            limite_teste = atoi(argv[2]);
            fprintf(stderr, " - Limitado a %d linhas na entrada\n\n\n", limite_teste);
        }
        else
        {
            limite_teste = INT_MAX;
        }    // end if
    }
    else
    {
        fprintf(stderr, "Usando entrada padrão\n");
        Entrada = stdin;
    }// end if

    buffer.pBuffer        = malloc((size_t)(_TAMANHO_BUFFER));
    buffer.disponiveis    = 0;
    buffer.proximo        = 0;
    buffer.arquivo        = Entrada;

    do
    {
        int t;
        status = uma_linha(linha, _LIMITE_LINHA, &buffer);
        if (status > 0)
        {    // leu uma linha: em branco?
            linhas_lidas++;
            if ((t = strlen(linha)) > 0)
            {    // tem algo na linha
                acha_o_nome(linhas_lidas, t, linha);
            }
            else
            {
                linhas_em_branco++;
                fprintf(stderr, "Linha %d: Linha em branco\n", linhas_lidas);
            }    // end if

            if (linhas_lidas < limite_teste) continue;
            fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
            break;
        }    // end if
    } while (status >=0);

    free(buffer.pBuffer);
    fclose(Entrada);
    printf("Final: Lidas %d linhas --- %d em branco\n", linhas_lidas, linhas_em_branco);
    return EXIT_SUCCESS;
  }

 

Roda tranquilo em Windows e provavelmente no linux sem qualquer mudança.

 

Sugiro testar o programa com o seu arquivo e ver o que acontece, ao menos com umas primeiras linhas, e depois com todas as linhas.

 

Até ++

 

 

 

 

 

 

 

 

 

 

 

Link para o comentário
Compartilhar em outros sites

10 horas atrás, arfneto disse:

exemplo.png.6c779bc79eca344e437739558431e52e.png

 

Entendo. Pergunto porque talvez você fosse o caso de usar um banco de dados se precisar fazer consultas mais elaboradas no futuro. sqlite é a opção simples. E se pudesse usar outra linguagem, C++ tem várias estruturas que poderia aproveitar para indexar tudo, como listas, filas, árvores e coisas assim. Só declarar e usar.

Sobre SQLite em C pode ver algo em https://www.sqlite.org/cintro.html.

 

Claro, em C é mais divertido :D

 

De volta ao tópico: Em relação ao seu exemplo:

 

exemplo.png.6c779bc79eca344e437739558431e52e.png

 

Acho mais produtivo usar então os argumentos direto na linha de comando, e vou deixar um exemplo com dois parâmetros, para usar assim --- imaginando que seu programa se chame cadastro:


cadastro arquivo linhas 

ou apenas 

cadastro

E se você chamar assim


cadastro arquivo.txt 200    le as primeiras 200 linhas do arquivo "arquivo.txt"
cadastro arquivo.doc        le todas as linhas de "arquivo.doc"
cadastro                    le os campos a partir da entrada padrão mesmo

Assim fica mais fácil de testar... E você pode editar direto uns arquivos de teste

 

Seguindo a lógica de que falei, escrevi uma parte de uma possível solução em puro e simples C. Vou postar aqui agora porque já pode ser útil. Falta implementar a estrutura de dados para acumular os valores e tenho uma ideia para uma solução simples que permitiria responder sua questão. Quando eu tiver um tempo volto a postar a solução, e aí completa. Se eu tiver tempo talvez escreva amanhã.

 

Eis o que tem aqui:

 

O programa lê o arquivo de entrada e extrai os dados, com a opção de limitar o número de linhas... Para essas 11 linhas em "c.txt"


2345678 nome  com    muitos      espacos   12345678
12345678 nome com espacos   12345678

ABCD
3401 LUCIA GONCALVES  0017786001 00000        BR00      00000


3401 LUCIA  GONCALVES  0017786001 00                       00000
3402    MARTA       PEREIRA DOS SANTOS 00177   00000
3401     LUCIA GONCALVES  0017786001 00000      00000
3402 MARTA PEREIRA DOS SANTOS 0017786002 00000 08

E usando cadastro c.txt 8

 

O programa gera

run-test.thumb.png.e1248285ae4770bc6a974b96ce79ff31.png

 

 

Como as linhas aparecem na tela você pode ir ao arquivo texto de origem e corrigir algo ou ver o que está errado no arquivo ou no programa e continuar refinando a solução. Ou se usa linux pode ir usando grep awk sort,  pr e ir criando seus relatórios sem programar nada.

 

Para ir a uma determinada linha do arquivo de entrada e que aparece na tela você pode, se usa linux e vi, digitar :n e ir para a linha n do arquivo direto. Se usa Visual Studio pode usar control-G e digitar a linha.

Vendo uma linha com mais detalhe:

linha.png.b1a5392ee7ad46860459c947d7f0e974.png

 

Eu uso Windows 10 e Visual Studio '19 e meu compilador não é assim o máximo para rodar C, e faz isso como uma cortesia :D digamos. Até tenho máquinas Linux na mesa de trás mas não tenho tempo para criar um ambiente nelas para essas coisas que faço com meu tempo livre...

 

De volta ao programa:

note o nome do programa na primeira linha: trata-se do argumento zero da linha de comando e sempre tem o nome do programa executável: argv[0]. Os próximos valores são opcionais e são aqueles que você digita na lionha de comando e vai entender ao ler a fonte em main().

Ai vem para cada linha da entrada o numero dela, o tamanho do nome e as posições de início e fim como o programa identificou. Logo depois uma linha mostra com asteriscos onde o programa acha que está o nome, para facilitar para conferir.

Depois o programa isola o nome, descartando o que não for letra à esquerda e à direita e depois troca qualquer sequência de espaços por um espaço apenas, para poder tratar o caso em que um mesmo nome tenha sido digitado com espaços a mais ou a menos. E converte todas as letras para minúsculas para ter certeza que não vai diferenciar nomes só por uma inicial maiúscula por exemplo. Essa última linha, que tem a tag (comprimido) é a que será usada para cadastrar o nome no banco de dados.

 

O programa tem essas 5 funções, alem de main()


int acha_o_nome(int, int, char*);
int ajusta_o_nome(int, char*);
int completa_buffer(Buffer*);
int trata_o_nome(int, char*);
int uma_linha(char*, const int, Buffer*);

e a lógica é bem simples. Não é a melhor solução, nem uma boa solução possivelmente, mas sugiro dar uma lida. Gira em torno da estrutura abaixo e gera os nomes para cadastrar depois e comparar. Na prática lê o arquivo em segmentos de um certo amanho --- como 32k no exemplo --- e aí vai caçando as linhas dentro do buffer. Quando acaba lê mais um pedaço e assim vai.


typedef struct
{
    unsigned char *        pBuffer;
    int                    disponiveis;
    int                    proximo;
    FILE *                 arquivo;
}
Buffer;

Eis o trecho de main() que implementa a lógica

 


 


    buffer.pBuffer        = malloc((size_t)(_TAMANHO_BUFFER));
    buffer.disponiveis    = 0;
    buffer.proximo        = 0;
    buffer.arquivo        = Entrada;

    do
    {
        int t;
        status = uma_linha(linha, _LIMITE_LINHA, &buffer);
        if (status > 0)
        {    // leu uma linha: em branco?
            linhas_lidas++;
            if ((t = strlen(linha)) > 0)
            {    // tem algo na linha
                acha_o_nome(linhas_lidas, t, linha);
            }
            else
            {
                linhas_em_branco++;
                fprintf(stderr, "Linha %d: Linha em branco\n", linhas_lidas);
            }    // end if

            if (linhas_lidas < limite_teste) continue;
            fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
            break;
        }    // end if
    } while (status >=0);

    free(buffer.pBuffer);
    fclose(Entrada);
    printf("Final: Lidas %d linhas --- %d em branco\n", linhas_lidas, linhas_em_branco);
    return EXIT_SUCCESS;
  }

Na verdade é quase o programa todo :D 

O programa chama uma_linha() para tentar ler a próxima linha e se consegue chama acha_o_nome() para isolar o nome e esta chama trata_o_nome() para continuar o serviço. Se já tem um plano para cadastrar os caras pode usar direto em ajusta_o_nome() que é onde o programa faz a compressão final do nome lido.

 

Depois escrevo uma opção para o cadastro e a identificação final dos nomes e com as duplicatas identificadas.

 

Eis o programa todo até aqui


#define    _CRT_SECURE_NO_WARNINGS
#define    _TAMANHO_BUFFER  (32768)
#define    _LIMITE_LINHA    (128)

#include "ctype.h"
#include "errno.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct
{
    unsigned char *        pBuffer;
    int                    disponiveis;
    int                    proximo;
    FILE *                arquivo;
}
Buffer;

typedef struct
{
    unsigned int        linhas_lidas;
    unsigned int        linhas_em_branco;
    unsigned int        nomes_validos;
}
Base_de_dados;

int acha_o_nome(int, int, char*);
int ajusta_o_nome(int, char*);
int completa_buffer(Buffer*);
int trata_o_nome(int, char*);
int uma_linha(char*, const int, Buffer*);

int acha_o_nome(int n, int t, char* l)
{
    // n = numero da linha
    // t = tamanho da linha
    // l = a linha
    int inicio = 0;
    int final = 0;
    int i;
    // linha em l com t caracteres, t>0
    for (inicio = 0; inicio < t; inicio++) if (!isdigit(l[inicio])) break;
    for (final = inicio; final < t; final++) if (isdigit(l[final])) break;
    if (inicio == final)
    {
        fprintf(stderr, "Linha %d: Nome nao identificado\n", n);
        return 1;
    }
    fprintf(stderr,"Linha %d: Nome com %d caracteres. Posicao [%d,%d]\n", n, (final - inicio), inicio, final);
    fprintf(stderr,"%s\n", l);
    for (i = 0; i < inicio; i++) fprintf(stderr, "-");
    for (i = inicio; i < final; i++) fprintf(stderr, "*");
    for (i = final; i < t; i++) fprintf(stderr, "-");
    fprintf(stderr, "\n");

    // cria uma string com o nome e passa para a rotina que vai 
    // cadastrar a entrada
    i = final - inicio + 1;
    char* pessoa = malloc((size_t) i);
    *(pessoa+i-1) = 0;    // pra nao esquecer: finaliza a string
    memcpy(pessoa, (l+inicio), (final-inicio) );
    fprintf(stderr, "[%s]\n", pessoa);

    trata_o_nome(n, pessoa);

    free(pessoa);
    return EXIT_SUCCESS;
}    // end acha_o_nome()


int ajusta_o_nome(int n, char* nome)
{
    // a partir de um nome ok converte caracteres e comprime espacos
    int        in_space = 0;        // usado para comprimir os espacos
    int        t = strlen(nome);
    char*    pVetor = NULL;        // ponteiro para a string de saida
    char*     vetor = malloc(t+1);

    // copia nome para vetor comprimindo os brancos ou tabs
    *vetor = tolower(*nome);    // copia o primeiro caracter
    pVetor = vetor + 1;
    *pVetor = 0;

    for (int i=1; i<t-1; i++)
    {
        if (isblank(nome[i]))
        {
            if(in_space==1)
            {
                continue;
            }
            else
            {
                in_space = 1;
                *pVetor = ' ';
                pVetor++;
                continue;
            }
        }
        else
        {
            in_space = 0;
            *pVetor = tolower(nome[i]);
            pVetor++;
        }    // end if
    }    // end for
    // copia o ultimo caracter, que com certeza nao era branco
    *pVetor = tolower(*(nome+t-1));
    pVetor++;
    *pVetor = 0;    // termina a string de saida
    fprintf(stderr, "Linha %d: Nome[%s] (comprimido)\n", n, vetor);
    free(vetor);
    return 0;
}    // end ajusta_o_nome()


int completa_buffer(Buffer* b)
{
    // retorna
    //  0 ao completar o buffer ou
    // -1 se EOF ou erro no arquivo
    unsigned char* p = b->pBuffer;
    // desloca para o inicio o que tinha sobrado no buffer
     for(int i=0; i<(b->disponiveis); i++)    *(p+i) = *(p+ i + b->proximo);
    int a_ler = _TAMANHO_BUFFER - b->disponiveis;    // tenta completar
    p = b->pBuffer + b->disponiveis;    // le a partir do que ja tinha
    int lidos = fread( p, 1, a_ler, b->arquivo );
    b->disponiveis = b->disponiveis + lidos;
    b->proximo = 0;
    if (lidos == 0)    return(-1);    else return 0;    // sinaliza final
}    // end completa_buffer()


int trata_o_nome(int n, char* nome)
{
    int t = strlen(nome);
    int inicio = 0;
    int final = 0;
    // linha em l com t caracteres, t>0
    for (inicio=0; inicio<t; inicio++)
    {
        if (isblank(nome[inicio]))
        {
            continue;
        }
        else
        {
            break;
        }    // end if
    }    //    end for
    
    if (inicio >= t)
    {    // pode estar toda em branco
        fprintf(stderr, "Linha %d: Nome [%s] em branco\n", n, nome);
        return -1;
    }

    for (final=(t-1); final>=inicio; final--)
    {
        if (!isblank(nome[final]))
        {
            break;
        }
        else
        {
            continue;
        }    // end if
    }    // end for
    if (inicio >= final) return 1;
    nome[final+1] = 0;    // trunca aqui
    fprintf(stderr, "Linha %d: Nome [%s]\n", n, nome + inicio);
    ajusta_o_nome(n, nome+inicio);
    fprintf(stderr, "__________ __________ __________ __________ __________ __________ \n\n");

    return 0;
}    // end trata_o_nome()


int uma_linha(    char* linha, const int maximo, Buffer* buf)
{
    //
    // retorna
    // - 1 e a linha em linha ou
    // - 0 se nao tem uma linha completa no buffer
    // - -1 se acabou o arquivo
    //
    int lidos;
    unsigned char* inicio = buf->pBuffer + buf->proximo;
    unsigned char* p = inicio;
    for (int i=0; i<buf->disponiveis; i++)
    {
        if (*p == '\n')
        {
            *p = 0;
            strcpy(linha, inicio);
            lidos = strlen(linha);
            buf->proximo += 1 + i;
            buf->disponiveis -= i+1;
            return 1;
        }
        else
        {
            p++;
        }    // end if
    }    // end for
    int n = completa_buffer(buf);
    return n;
}    // end uma_linha()

int main(int argc, char** argv)
{
    FILE*            Entrada = NULL;
    Buffer            buffer;
    Base_de_dados    base;
    int                linhas_lidas = 0;
    int                linhas_em_branco = 0;
    int                status = 0;
    char            linha[256];
    int                limite_teste;    // para em n linhas do arquivo

    printf("\n\nRodando: %s\n\n\n", argv[0]);
    if (argc > 1)
    {
        Entrada = fopen(argv[1], "r");
        if (Entrada == NULL)
        {
            fprintf(stderr, "Erro abrindo %s\n", argv[1]);
            return 0;
        }    // end if
        fprintf(stderr, " - Lendo a partir do arquivo %s\n", argv[1]);
        if (argc > 2)
        {
            limite_teste = atoi(argv[2]);
            fprintf(stderr, " - Limitado a %d linhas na entrada\n\n\n", limite_teste);
        }
        else
        {
            limite_teste = INT_MAX;
        }    // end if
    }
    else
    {
        fprintf(stderr, "Usando entrada padrão\n");
        Entrada = stdin;
    }// end if

    buffer.pBuffer        = malloc((size_t)(_TAMANHO_BUFFER));
    buffer.disponiveis    = 0;
    buffer.proximo        = 0;
    buffer.arquivo        = Entrada;

    do
    {
        int t;
        status = uma_linha(linha, _LIMITE_LINHA, &buffer);
        if (status > 0)
        {    // leu uma linha: em branco?
            linhas_lidas++;
            if ((t = strlen(linha)) > 0)
            {    // tem algo na linha
                acha_o_nome(linhas_lidas, t, linha);
            }
            else
            {
                linhas_em_branco++;
                fprintf(stderr, "Linha %d: Linha em branco\n", linhas_lidas);
            }    // end if

            if (linhas_lidas < limite_teste) continue;
            fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
            break;
        }    // end if
    } while (status >=0);

    free(buffer.pBuffer);
    fclose(Entrada);
    printf("Final: Lidas %d linhas --- %d em branco\n", linhas_lidas, linhas_em_branco);
    return EXIT_SUCCESS;
  }

 

Roda tranquilo em Windows e provavelmente no linux sem qualquer mudança.

 

Sugiro testar o programa com o seu arquivo e ver o que acontece, ao menos com umas primeiras linhas, e depois com todas as linhas.

 

Até ++

 

 

 

 

 

 

 

 

 

 

 

Demais esse programa, vou levar um mês para entender todo ele..ficou 10.

Link para o comentário
Compartilhar em outros sites

Em 27/08/2019 às 10:33, nigolino disse:

Demais esse programa, vou levar um mês para entender todo ele..ficou 10.

 

:D

Nem tanto

 

Prosseguindo... Temos uma solução quase pronta, e um exemplo de como programar estruturas de dados em C, do zero

 

Criei uma estrutura de exemplo para o seu problema e escrevi um programa de teste, Ainda não coloquei no contexto do arquivo de entrada, o que é simples: basta ir em ajustar_o_nome() e quando tem o nome "comprimido" chamar a função que insere no cadastro, passando obviamente o nome e a linha do arquivo em que o nome foi encontrado.
O programa mantem uma lista atualizada dos nomes e dentro dela uma lista de duplicatas com o numero da linha no arquivo original, assim a gente pode ir lá e conferir usando um editor de texto. :n no vi vai pra linha por exemplo, no linux. Control-G n vai para a linha n no Visual Studio :D. Chega de exemplos

.

Programando essas coisas em C fica fácil de ver a vantagem de usar uma linguagem com suporte a objetos e que já tenha essas estruturas prontas só para declarar e usar... 


De todo modo, eis uma estrutura que resolve o problema:

typedef struct
{
    unsigned int        linhas_lidas;
    unsigned int        linhas_em_branco;
    unsigned int        nomes_unicos;
    unsigned int        nomes_duplicados;
    Cadastro*           cadastro;
} Base_de_dados;

Você declara uma variável dessas e a coisa anda.

Algo assim

Base_de_dados    base;

O cadastro em si é uma lista ligada dupla, uma lista em que cada elemento aponta para o anterior e o próximo. Talvez não precisasse apontar para o anterior no nosso caso, mas assim fica mais útil para outras funções no futuro

typedef struct
{
    char*               nome;
    unsigned int        linha_original;
    unsigned int        duplicatas;
    struct Dup*         lista_duplicados;
    struct Cadastro*    proximo;
    struct Cadastro*    anterior;
} Cadastro;

Cada base de dados tem o seu cadastro. claro. Assim hipoteticamente você poderia usar ao mesmo tempo vários cadastros em seu programa. Sim, como uma classe em outras linguagens como java ou C++.

Cada nome no cadastro tem uma lista de eventuais duplicatas, bem simples. Tem apenas o número da linha e aponta para uma eventual próxima duplicata.

typedef struct
{
    int        duplicata;
    struct Dup* proxima;
} Dup;

Claro, cada nome tem a sua lista de duplicatas.Isso fecha o círculo em termos de dados.

 

Escrevi duas funções apenas:

Cadastro* t_insere_cadastro(Cadastro* cadastro, char* cliente, unsigned int linha);
int       t_lista_cadastro(Cadastro* cad);

Uma insere no cadastro 'cadastro' o nome 'cliente' que esta na linha 'linha'. A outra lista um cadastro cad na tela, para testar. Note que o programa permite desde antes limitar o número de linhas a serem lidas então fica fácil de ir testando, porque você pode editar arquivos de teste a vontade.

 

Fica fácil escrever rotinas de teste para a lista. Veja uma:

int t_testa_cadastro(Cadastro* cad)
{
    // teste
    Cadastro    teste;
    Cadastro* pc = &teste;

    pc->nome = NULL;
    pc->linha_original = 0;
    pc->duplicatas = 0;
    pc->lista_duplicados = NULL;
    pc->anterior = NULL;
    pc->proximo = NULL;

    // testa para cadastro nao criado
    t_lista_cadastro(NULL);

    // testa para cadastro vazio
    t_lista_cadastro(pc);

    // cria umas duplicatas
    pc = t_insere_cadastro(pc, "B", 1);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 2);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 3);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 43);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 343);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    // insere no fim
    pc = t_insere_cadastro(pc, "C", 3);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);
    // insere no comeco

    pc = t_insere_cadastro(pc, "A", 800);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);
    return 0;
}    // end testa_cadastro()


Essa função testa várias coisas: cadastro nulo, vazio, quatro cópias do nome B, insere A que vai pro início, C que vai pro fim e lista tudo. As funções de teste mostram na tela o funcionamento do esquema e por isso não coloquei no programa ainda: para o uso normal é preciso tirar essas mensagens todas. Depois eu vou postar uma versão completa que faz todo o serviço.

Eis o final da listagem para esse teste

***** inserindo: [A] (linha 800)
    Tentando inserir [A]
    o Proximo da lista: [B]
    [B] maior: insere aqui
    [A] vai ser o novo primeiro
    inserido [A] NOVA ORIGEM
Origem: A

Listando:
----------    ----------    ----------    ----------
Nome:.......[A]
    Linha:.......[800]
Nome:.......[B]
    Linha:.......[1]
 N. Duplicadas:..[4]
         1: [Linha 2]
         2: [Linha 3]
         3: [Linha 43]
         4: [Linha 343]
Nome:.......[C]
    Linha:.......[3]

Fim da Lista

Não ia querer ler isso para 45.000 nomes afinal :D
Mas o que importa é que ao final das inserções os nomes estão em ordem, A B C, e as duplicatas de B estão lá na lista com os números de linha certinhos.

Não tenho tempo agora para discutir o código que lista os caras, ou o código chato mas muito importante que insere. Nem para inserir isso na leitura do arquivo e terminar o programa. Só estou postando porque acho bem instrutivo para essas funções na versão COM as mensagens na tela explicando o andamento.

 

Essas estruturas são muito usadas em programação, claro. E na vida em geral, afinal são listas. 

 

Problema ou solução

É claro que é algo poderoso e que resolve um milhão de situações, mas entenda que não tem persistência: encerrou o programa acabou a informação. Tem que ler o arquivo e fazer tudo de novo. Por isso um banco de dados é tão cômodo. Aqui seria preciso programar ainda muitas funções para por exemplo salvar em disco e ler de novo, ou mesmo excluir nomes...

O que falta?

Falta apenas criar novas versões das funções que listam e inserem os dados. Versões sem as mensagens de acompanhamento, só isso. E ir lá na rotina que comprime os espaços e converte o nome e inserir cada nome no cadastro. Só isso. E poderá acessar os duplicados facilmente no cadastro. Ou fazer qualquer coisa com os nomes afinal.

Outra hora vou postar o programa todo numa maneira mais simples e já funcionando

Seguem as funções e o programa completo

Lista

int t_lista_cadastro(Cadastro* cad)
{
    if (cad == NULL)
    {
        printf("\nListando: Cadastro ainda nao alocado\n");
        return 0;
    }    // end if

    Cadastro* p = cad;

    if (p->nome == NULL)
    {
        printf("\nListando:Cadastro vazio\n");
        return 0;
    }    // end if

    // lista tem ao menos um cliente
    printf("\nListando:\n----------    ----------    ----------    ----------    \n");
    do
    {
        printf("Nome:.......[%s]\n", p->nome);
            printf("    Linha:.......[%d]\n", p->linha_original);
        if (p->duplicatas > 0)
        {
                printf(" N. Duplicadas:..[%d]\n", p->duplicatas);
            Dup* pDup = p->lista_duplicados;
            for (int n=1; n<=p->duplicatas; n++)
            {
                printf("         %d: [Linha %d]\n", n, pDup->duplicata );
                pDup = pDup->proxima;
            }    // end for
        }    // end if
    } while ((p = (Cadastro*) p->proximo) != NULL);
    printf("\nFim da Lista\n\n");
    return 0;
}    // end t_lista_cadastro()

Insere

Cadastro* t_insere_cadastro(Cadastro* cad, char * cliente, unsigned int linha)
{
    if (cad == NULL)
    {
        printf("***** inserindo: Cadastro nao definido\n");
        return NULL;
    }
    Cadastro* p = cad;
    int len = strlen(cliente) + 1;
    printf("***** inserindo: [%s] (linha %d)\n", cliente, linha);
    char* novo_nome = (char*) malloc(len);
    strcpy(novo_nome, cliente);

    if (p->nome == NULL)    // nesse caso nao tem ninguem
    {
        printf("    (Primeiro item)\n");
        p->nome = novo_nome;
        p->linha_original = linha;
        p->duplicatas = 0;
        p->lista_duplicados = NULL;
        p->anterior = NULL;
        p->proximo = NULL;
        return p;
    }    // end if
    //
    // ja tem alguem na lista: tem que achar o lugar certo pra inserir
    // estamos no inicio entao caminhamos ate encontrar a posicao
    // usando strmcmp() até encontrar o primeiro nome maior que o 
    // que temos aqui, ou até chegar ao fim da lista, claro.
    //
    // acertando os ponteiros:
    // 
    // estamos para inserir 'novo_nome' na lista
    // o primeiro nome na lista e 'p->nome
    //
    printf("    Tentando inserir [%s]\n", novo_nome);
    printf("    o Proximo da lista: [%s]\n", p->nome);

    // cria o registro novo
    Cadastro* pNovo = (Cadastro*) malloc(sizeof(Cadastro));
    pNovo->nome = novo_nome;
    pNovo->linha_original = linha;
    pNovo->duplicatas = 0;
    pNovo->lista_duplicados = NULL;
    pNovo->anterior = NULL;
    pNovo->proximo = NULL;

    while (1)
    {
        int n = strcmp(p->nome, novo_nome);
        // entao o novo nome eh maior: avanca na lista
        if (n < 0)
        {
            printf("    [%s] menor: avancando\n", p->nome);
            if (p->proximo == NULL)
            {
                printf("    [%s] e o ultimo cliente. Insere no final\n", p->nome);
                p->proximo = (Cadastro*) pNovo;
                pNovo->anterior = (Cadastro*)p;
                printf("    inserido [%s] no final\n", novo_nome);
                return cad;    // mesma origem
            }
            p = (Cadastro*) p->proximo;
            continue;
        }    // end if
        if (n > 0)
        {
            // é aqui
            printf("    [%s] maior: insere aqui\n", p->nome);
            // pegadinha: se vai inserir antes do primeiro e diferente
            if (p->anterior == NULL)
            {
                printf("    [%s] vai ser o novo primeiro\n", pNovo->nome);
                pNovo->anterior = NULL;
                pNovo->proximo = (Cadastro*)p;
                p->anterior = (Cadastro *)pNovo;
                // p-> proximo nao muda
                // o segundo apontava para o primeiro
                Cadastro* segundo = (Cadastro*) p->proximo;
                segundo->anterior = (Cadastro*) pNovo;
                printf("    inserido [%s] NOVA ORIGEM\n", novo_nome);
                return pNovo;
            }
            printf("    [%s] vai ser inserido no meio da lista\n", pNovo->nome);
            // insere pNovo antes de p


            Cadastro* outro = (Cadastro*) p->anterior;
            outro->proximo = (Cadastro*) pNovo;

            pNovo->anterior = (Cadastro*) outro;
            pNovo->proximo = (Cadastro*)p;

            outro = (Cadastro*)p->proximo;
            outro->anterior = (Cadastro*)pNovo;

            p->anterior = (Cadastro*)pNovo;
            // p-> proximo nao muda
            printf("    inserido [%s] no meio da lista\n", novo_nome);

            return cad;
        }    // end if
        // nem maior nem menor: sao iguais. Cadastra duplicado
        printf("    [%s] igual: cadastra duplicata\n", p->nome);
        free(novo_nome);
        free(pNovo);
        // cria novo registro de duplicata
        Dup* pDup = NULL;
        Dup* pNovo_dup = (Dup*)malloc(sizeof(Dup));
        pNovo_dup->duplicata = linha;
        pNovo_dup->proxima = NULL;
        p->duplicatas += 1;
        if (p->lista_duplicados == NULL)
        {    // se e a primeira duplicata fica fácil
            p->lista_duplicados = pNovo_dup;
        }
        else
        {    // nao e a primeira: insere no final
            Dup* pDup = p->lista_duplicados;
            while (pDup->proxima != NULL) pDup = pDup->proxima;
            pDup->proxima = pNovo_dup;
        }    // end if
        printf("    [%s] igual: cadastrada como duplicata %d\n", p->nome, p->duplicatas);
        return cad;
    }
    return cad;
}    // end t_insere_cadastro()

O programa inteiro

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define    _TAMANHO_BUFFER    (8192)
#define    _LIMITE_LINHA    (128)

#include "ctype.h"
#include "errno.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct
{
    unsigned char*    pBuffer;
    int                disponiveis;
    int                proximo;
    FILE*            arquivo;
} Buffer;

typedef struct
{
    int            duplicata;
    struct Dup* proxima;
} Dup;

typedef struct
{
    char*                nome;
    unsigned int        linha_original;
    unsigned int        duplicatas;
    struct Dup*            lista_duplicados;
    struct Cadastro*    proximo;
    struct Cadastro*    anterior;
} Cadastro;

typedef struct
{
    unsigned int        linhas_lidas;
    unsigned int        linhas_em_branco;
    unsigned int        nomes_unicos;
    unsigned int        nomes_duplicados;
    Cadastro*             cadastro;
} Base_de_dados;

int acha_o_nome(int, int, char*);
int ajusta_o_nome(int, char*);
int completa_buffer(Buffer*);
int trata_o_nome(int, char*);
int uma_linha(char*, const int, Buffer*);

Cadastro*    t_insere_cadastro(Cadastro*, char*, unsigned int);
int            t_lista_cadastro(Cadastro*);
int            t_testa_cadastro(Cadastro*);


int acha_o_nome(int n, int t, char* l)
{
    // n = numero da linha
    // t = tamanho da linha
    // l = a linha
    int inicio = 0;
    int final = 0;
    int i;
    // linha em l com t caracteres, t>0
    for (inicio = 0; inicio < t; inicio++) if (!isdigit(l[inicio])) break;
    for (final = inicio; final < t; final++) if (isdigit(l[final])) break;
    if (inicio == final)
    {
        fprintf(stderr, "Linha %d: Nome nao identificado\n", n);
        return 1;
    }
    fprintf(stderr,"Linha %d: Nome com %d caracteres. Posicao [%d,%d]\n", n, (final - inicio), inicio, final);
    fprintf(stderr,"%s\n", l);
    for (i = 0; i < inicio; i++) fprintf(stderr, "-");
    for (i = inicio; i < final; i++) fprintf(stderr, "*");
    for (i = final; i < t; i++) fprintf(stderr, "-");
    fprintf(stderr, "\n");

    // cria uma string com o nome e passa para a rotina que vai 
    // cadastrar a entrada
    i = final - inicio + 1;
    char* pessoa = malloc((size_t) i);
    *(pessoa+i-1) = 0;    // pra nao esquecer: finaliza a string
    memcpy(pessoa, (l+inicio), (final-inicio) );
    fprintf(stderr, "[%s]\n", pessoa);

    trata_o_nome(n, pessoa);

    free(pessoa);
    return EXIT_SUCCESS;
}    // end acha_o_nome()


int ajusta_o_nome(int n, char* nome)
{
    // a partir de um nome ok converte caracteres e comprime espacos
    int        in_space = 0;        // usado para comprimir os espacos
    int        t = strlen(nome);
    char*    pVetor = NULL;        // ponteiro para a string de saida
    char*     vetor = malloc(t+1);

    // copia nome para vetor comprimindo os brancos ou tabs
    *vetor = tolower(*nome);    // copia o primeiro caracter
    pVetor = vetor + 1;
    *pVetor = 0;

    for (int i=1; i<t-1; i++)
    {
        if (isblank(nome[i]))
        {
            if(in_space==1)
            {
                continue;
            }
            else
            {
                in_space = 1;
                *pVetor = ' ';
                pVetor++;
                continue;
            }
        }
        else
        {
            in_space = 0;
            *pVetor = tolower(nome[i]);
            pVetor++;
        }    // end if
    }    // end for
    // copia o ultimo caracter, que com certeza nao era branco
    *pVetor = tolower(*(nome+t-1));
    pVetor++;
    *pVetor = 0;    // termina a string de saida
    fprintf(stderr, "Linha %d: Nome[%s] (comprimido)\n", n, vetor);
    free(vetor);
    return 0;
}    // end ajusta_o_nome()


int completa_buffer(Buffer* b)
{
    // retorna
    //  0 ao completar o buffer ou
    // -1 se EOF ou erro no arquivo
    unsigned char* p = b->pBuffer;
    // desloca para o inicio o que tinha sobrado no buffer
     for(int i=0; i<(b->disponiveis); i++)    *(p+i) = *(p+ i + b->proximo);
    int a_ler = _TAMANHO_BUFFER - b->disponiveis;    // tenta completar
    p = b->pBuffer + b->disponiveis;    // le a partir do que ja tinha
    int lidos = fread( p, 1, a_ler, b->arquivo );
    b->disponiveis = b->disponiveis + lidos;
    b->proximo = 0;
    if (lidos == 0)    return(-1);    else return 0;    // sinaliza final
}    // end completa_buffer()


int trata_o_nome(int n, char* nome)
{
    int t = strlen(nome);
    int inicio = 0;
    int final = 0;
    // linha em l com t caracteres, t>0
    for (inicio=0; inicio<t; inicio++)
    {
        if (isblank(nome[inicio]))
        {
            continue;
        }
        else
        {
            break;
        }    // end if
    }    //    end for
    
    if (inicio >= t)
    {    // pode estar toda em branco
        fprintf(stderr, "Linha %d: Nome [%s] em branco\n", n, nome);
        return -1;
    }

    for (final=(t-1); final>=inicio; final--)
    {
        if (!isblank(nome[final]))
        {
            break;
        }
        else
        {
            continue;
        }    // end if
    }    // end for
    if (inicio >= final) return 1;
    nome[final+1] = 0;    // trunca aqui
    fprintf(stderr, "Linha %d: Nome [%s]\n", n, nome + inicio);
    ajusta_o_nome(n, nome+inicio);
    fprintf(stderr, "__________ __________ __________ __________ __________ __________ \n\n");

    return 0;
}    // end trata_o_nome()


int uma_linha(    char* linha, const int maximo, Buffer* buf)
{
    //
    // retorna
    // - 1 e a linha em linha ou
    // - 0 se nao tem uma linha completa no buffer
    // - -1 se acabou o arquivo
    //
    int lidos;
    unsigned char* inicio = buf->pBuffer + buf->proximo;
    unsigned char* p = inicio;
    for (int i=0; i<buf->disponiveis; i++)
    {
        if (*p == '\n')
        {
            *p = 0;
            strcpy(linha, inicio);
            lidos = strlen(linha);
            buf->proximo += 1 + i;
            buf->disponiveis -= i+1;
            return 1;
        }
        else
        {
            p++;
        }    // end if
    }    // end for
    int n = completa_buffer(buf);
    return n;
}    // end uma_linha()


Cadastro* t_insere_cadastro(Cadastro* cad, char * cliente, unsigned int linha)
{
    if (cad == NULL)
    {
        printf("***** inserindo: Cadastro nao definido\n");
        return NULL;
    }
    Cadastro* p = cad;
    int len = strlen(cliente) + 1;
    printf("***** inserindo: [%s] (linha %d)\n", cliente, linha);
    char* novo_nome = (char*) malloc(len);
    strcpy(novo_nome, cliente);

    if (p->nome == NULL)    // nesse caso nao tem ninguem
    {
        printf("    (Primeiro item)\n");
        p->nome = novo_nome;
        p->linha_original = linha;
        p->duplicatas = 0;
        p->lista_duplicados = NULL;
        p->anterior = NULL;
        p->proximo = NULL;
        return p;
    }    // end if
    //
    // ja tem alguem na lista: tem que achar o lugar certo pra inserir
    // estamos no inicio entao caminhamos ate encontrar a posicao
    // usando strmcmp() até encontrar o primeiro nome maior que o 
    // que temos aqui, ou até chegar ao fim da lista, claro.
    //
    // acertando os ponteiros:
    // 
    // estamos para inserir 'novo_nome' na lista
    // o primeiro nome na lista e 'p->nome
    //
    printf("    Tentando inserir [%s]\n", novo_nome);
    printf("    o Proximo da lista: [%s]\n", p->nome);

    // cria o registro novo
    Cadastro* pNovo = (Cadastro*) malloc(sizeof(Cadastro));
    pNovo->nome = novo_nome;
    pNovo->linha_original = linha;
    pNovo->duplicatas = 0;
    pNovo->lista_duplicados = NULL;
    pNovo->anterior = NULL;
    pNovo->proximo = NULL;

    while (1)
    {
        int n = strcmp(p->nome, novo_nome);
        // entao o novo nome eh maior: avanca na lista
        if (n < 0)
        {
            printf("    [%s] menor: avancando\n", p->nome);
            if (p->proximo == NULL)
            {
                printf("    [%s] e o ultimo cliente. Insere no final\n", p->nome);
                p->proximo = (Cadastro*) pNovo;
                pNovo->anterior = (Cadastro*)p;
                printf("    inserido [%s] no final\n", novo_nome);
                return cad;    // mesma origem
            }
            p = (Cadastro*) p->proximo;
            continue;
        }    // end if
        if (n > 0)
        {
            // é aqui
            printf("    [%s] maior: insere aqui\n", p->nome);
            // pegadinha: se vai inserir antes do primeiro e diferente
            if (p->anterior == NULL)
            {
                printf("    [%s] vai ser o novo primeiro\n", pNovo->nome);
                pNovo->anterior = NULL;
                pNovo->proximo = (Cadastro*)p;
                p->anterior = (Cadastro *)pNovo;
                // p-> proximo nao muda
                // o segundo apontava para o primeiro
                Cadastro* segundo = (Cadastro*) p->proximo;
                segundo->anterior = (Cadastro*) pNovo;
                printf("    inserido [%s] NOVA ORIGEM\n", novo_nome);
                return pNovo;
            }
            printf("    [%s] vai ser inserido no meio da lista\n", pNovo->nome);
            // insere pNovo antes de p


            Cadastro* outro = (Cadastro*) p->anterior;
            outro->proximo = (Cadastro*) pNovo;

            pNovo->anterior = (Cadastro*) outro;
            pNovo->proximo = (Cadastro*)p;

            outro = (Cadastro*)p->proximo;
            outro->anterior = (Cadastro*)pNovo;

            p->anterior = (Cadastro*)pNovo;
            // p-> proximo nao muda
            printf("    inserido [%s] no meio da lista\n", novo_nome);

            return cad;
        }    // end if
        // nem maior nem menor: sao iguais. Cadastra duplicado
        printf("    [%s] igual: cadastra duplicata\n", p->nome);
        free(novo_nome);
        free(pNovo);
        // cria novo registro de duplicata
        Dup* pDup = NULL;
        Dup* pNovo_dup = (Dup*)malloc(sizeof(Dup));
        pNovo_dup->duplicata = linha;
        pNovo_dup->proxima = NULL;
        p->duplicatas += 1;
        if (p->lista_duplicados == NULL)
        {    // se e a primeira duplicata fica fácil
            p->lista_duplicados = pNovo_dup;
        }
        else
        {    // nao e a primeira: insere no final
            Dup* pDup = p->lista_duplicados;
            while (pDup->proxima != NULL) pDup = pDup->proxima;
            pDup->proxima = pNovo_dup;
        }    // end if
        printf("    [%s] igual: cadastrada como duplicata %d\n", p->nome, p->duplicatas);
        return cad;
    }
    return cad;
}    // end t_insere_cadastro()


int t_lista_cadastro(Cadastro* cad)
{
    if (cad == NULL)
    {
        printf("\nListando: Cadastro ainda nao alocado\n");
        return 0;
    }    // end if

    Cadastro* p = cad;

    if (p->nome == NULL)
    {
        printf("\nListando:Cadastro vazio\n");
        return 0;
    }    // end if

    // lista tem ao menos um cliente
    printf("\nListando:\n----------    ----------    ----------    ----------    \n");
    do
    {
        printf("Nome:.......[%s]\n", p->nome);
            printf("    Linha:.......[%d]\n", p->linha_original);
        if (p->duplicatas > 0)
        {
                printf(" N. Duplicadas:..[%d]\n", p->duplicatas);
            Dup* pDup = p->lista_duplicados;
            for (int n=1; n<=p->duplicatas; n++)
            {
                printf("         %d: [Linha %d]\n", n, pDup->duplicata );
                pDup = pDup->proxima;
            }    // end for
        }    // end if
    } while ((p = (Cadastro*) p->proximo) != NULL);
    printf("\nFim da Lista\n\n");
    return 0;
}    // end t_lista_cadastro()


int t_testa_cadastro(Cadastro* cad)
{
    // teste
    Cadastro    teste;
    Cadastro* pc = &teste;

    pc->nome = NULL;
    pc->linha_original = 0;
    pc->duplicatas = 0;
    pc->lista_duplicados = NULL;
    pc->anterior = NULL;
    pc->proximo = NULL;

    // testa para cadastro nao criado
    t_lista_cadastro(NULL);
    // testa para cadastro vazio
    t_lista_cadastro(pc);
    // cria umas duplicatas
    pc = t_insere_cadastro(pc, "B", 1);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 2);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 3);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 43);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);

    pc = t_insere_cadastro(pc, "B", 343);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);
    // insere no fim
    pc = t_insere_cadastro(pc, "C", 3);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);
    // inserre no comeco
    pc = t_insere_cadastro(pc, "A", 800);
    printf("Origem: %s\n", pc->nome);
    t_lista_cadastro(pc);
    return 0;
}    // end testa_cadastro()


int main(int argc, char** argv)
{
    FILE*            Entrada = NULL;
    Buffer            buffer;
    Base_de_dados    base;
    int                linhas_lidas = &base.linhas_lidas;
    int                linhas_em_branco = &base.linhas_em_branco;
    int                status = 0;
    char            linha[256];
    int                limite_teste;    // para em n linhas do arquivo

    printf("\n\nRodando: %s\n\n\n", argv[0]);
    if (argc > 1)
    {
        Entrada = fopen(argv[1], "r");
        if (Entrada == NULL)
        {
            fprintf(stderr, "Erro abrindo %s\n", argv[1]);
            return 0;
        }    // end if
        fprintf(stderr, " - Lendo a partir do arquivo %s\n", argv[1]);
        if (argc > 2)
        {
            limite_teste = atoi(argv[2]);
            fprintf(stderr, " - Limitado a %d linhas na entrada\n\n\n", limite_teste);
        }
        else
        {
            limite_teste = INT_MAX;
        }    // end if
    }
    else
    {
        fprintf(stderr, "Usando entrada padrão\n");
        Entrada = stdin;
    }// end if

    status = 0;

    base.linhas_em_branco = 0;
    base.linhas_lidas = 0;
    base.nomes_duplicados = 0;
    base.nomes_unicos = 0;
    base.cadastro = (Cadastro*) malloc(sizeof(Cadastro));

    Cadastro* cad = base.cadastro;
    t_testa_cadastro(cad);
    if (status == 0) return 0;

    buffer.pBuffer        = malloc((size_t)(_TAMANHO_BUFFER));
    buffer.disponiveis    = 0;
    buffer.proximo        = 0;
    buffer.arquivo        = Entrada;

    do
    {
        size_t t;
        status = uma_linha(linha, _LIMITE_LINHA, &buffer);
        if (status > 0)
        {    // leu uma linha: em branco?
            linhas_lidas++;
            if (t =  strlen(linha) > 0)
            {    // tem algo na linha
                acha_o_nome(linhas_lidas, t, linha);
            }
            else
            {
                linhas_em_branco++;
                fprintf(stderr, "Linha %d: Linha em branco\n", linhas_lidas);
            }    // end if

            if (linhas_lidas < limite_teste) continue;
            fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
            break;
        }    // end if
    } while (status >=0);

    free(buffer.pBuffer);
    fclose(Entrada);
    printf("Final: Lidas %d linhas --- %d em branco\n", linhas_lidas, linhas_em_branco);
    return EXIT_SUCCESS;
  }

 

Link para o comentário
Compartilhar em outros sites

Em 27/08/2019 às 10:33, nigolino disse:

Demais esse programa, vou levar um mês para entender todo ele..ficou 10.

 

Nem tanto.

 

Escrevi uma versão que trata o arquivo afinal, e vou postar aqui o link para download ao invés desses posts intermináveis :D 

Apenas criei as funções que tratam os nomes e as listas usando a estrutura de que falei antes.

 

Eis uma saída do programa:

Rodando: cadastro

Final:
    Lidas 18 linhas
          3 em branco
          7 duplicados
          8 nomes unicos

Listando:
Linha    Nome
-----   ------------------------------------------------
17    [aaaa]
        +1
        #1: [18]
9    [abcd]
10    [lucia goncalves]
        +2
        #1: [13]
        #2: [15]
14    [marta pereira dos santos]
        +1
        #1: [16]
6    [nome com menos espacos]
1    [nome com muitos espacos]
        +3
        #1: [3]
        #2: [4]
        #3: [5]
2    [nome com poucos espacos]
7    [nome sem espacos]

Fim da Lista

Para este arquivo de entrada 

2345678 nome  com    muitos      espacos   12345678
2345678nome  com poucos espacos 12345678
2345678 nome  com  muitos      espacos   12345678
2345678 nome  com    muitos  espacos   12345678
2345678 nome  com    muitos      espacos   12345678
12345678 nome com menos espacos    12345678
12345678nome sem espacos12345678

ABCD
3401 LUCIA GONCALVES  0017786001 00000        BR00      00000


3401 LUCIA  GONCALVES  0017786001 00                       00000
3402    MARTA       PEREIRA DOS SANTOS 00177   00000
3401     LUCIA GONCALVES  0017786001 00000      00000
3402 MARTA PEREIRA DOS SANTOS 0017786002 00000 08
23AAAA23

A mudança em relação ao programa de antes é pequena. Na função que ajusta o nome e mostrava isso

 

ch-190824-saida-nome.png.a78a73ff891c4b84445a50bcfe358179.png

 

foi acrescentada uma linha apenas

  

 fprintf(stderr, "Linha %d: Nome[%s] (comprimido)\n", n, vetor);
    //
    // tem o nome certinho: cadastra no banco
    //
    cadastra_o_nome(n, vetor);    // essa linha

 

E essa função cadastra_o_nome()? Usei só pra facilitar

 

int cadastra_o_nome(int linha, char* nome)
{
    fprintf(stderr,
        "***** cadastra o nome [%s] linha original [%d]\n",
        nome,
        linha
    );
    if (base.cadastro == NULL) return -1;
    base.cadastro = l_insere_cadastro(base.cadastro, nome, linha);
    return 0;
};    // end cadastra_o_nome();

Só escrevi duas funções para manipular as listas, 

// listas
Cadastro*      l_insere_cadastro(Cadastro*, char*, unsigned int const);
int            l_lista_cadastro(Cadastro*);

A função que insere é um pouco difícil de ler porque tem vários casos, inserir no começo, no fim, no meio, duplicatas e tal, mas não é nada demais. O código está lá. Se alguém chegar a ler e tiver alguma dúvida, pode me perguntar

 

A que lista vou deixar como exemplo:

int l_lista_cadastro(Cadastro* cad)
{
    if (cad == NULL)
    {
        printf("\nListando: Cadastro ainda nao alocado\n");
        return 0;
    }    // end if

    Cadastro* p = cad;

    if (p->nome == NULL)
    {
        printf("\nListando:Cadastro vazio\n");
        return 0;
    }    // end if

    // lista tem ao menos um cliente
    printf("\nListando:\nLinha\tNome\n-----   ------------------------------------------------\n");
    do
    {
        printf("%d\t[%s]\n", p->linha_original, p->nome);
        if (p->duplicatas > 0)
        {
            printf("\t\t+%d\n", p->duplicatas);
            Dup* pDup = p->lista_duplicados;
            for (unsigned int n = 1; n <= p->duplicatas; n++)
            {
                printf("\t\t#%d: [%d]\n", n, pDup->duplicata);
                pDup = pDup->proxima;
            }    // end for
        }    // end if
    } while ((p = (Cadastro*)p->proximo) != NULL);
    printf("\nFim da Lista\n\n");
    return 0;
}    // end t_lista_cadastro()


O programa principal é bem óbvio:

Eis a parte que monta a lista

    do
    {
        status = uma_linha(pLinha, _LIMITE_LINHA, &buffer);
        if (status == 0) continue;
        base.linhas_lidas++;
        if ((t= strlen(pLinha)) > 0)
        {    // tem algo na linha
            acha_o_nome(base.linhas_lidas, (int) t,pLinha);
        }
        else
        {    // em branco
            base.linhas_em_branco++;
            fprintf(stderr, "Linha %d: Linha em branco\n", base.linhas_lidas);
        }    // end if

        if (base.linhas_lidas < (unsigned) limite_teste) continue;
        fprintf(stderr, "\n\n\n***** atingido limite de %d linhas *****\n", limite_teste);
        break;
    } while (status >=0);


Mostra a lista

 

   l_lista_cadastro(base.cadastro);


 

E termina.

 

Pode ver  aqui, onde tem um botao de download

 

Pode rodar isso com o seu arquivo de nomes e ver o que acha. E para fazer algo objetivo basta escrever na última linha do programa, no lugar da função de apenas lista os caras.

 

Listar os duplicados 

Por exemplo, se quer listar os duplicados  basta percorrer a estrutura e seguir as listas de duplicados. Para conveniência lá está o número de cada linha.

 

Extrair os nomes únicos

Para extrair os nomes únicos não poderia ser mais simples: apenas segue a lista, que já vai estar ordenada.

Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...