Um estacionamento possui uma cancela eletrônica que emite tíquetes com código de barras quando o usuário entra no estacionamento e outra cancela que lê os tíquetes quando os usuários saem do estacionamento. Cada uma das duas cancelas possui um sistema que grava em um arquivo texto a data, hora e o número identificador do código de barras (ID). A cancela de entrada gera o ID e o imprime no tíquete de estacionamento e a cancela de saída lê o ID do tíquete para gravá-lo no arquivo. Considere que os relógios de ambas as cancelas estão sincronizados. Segue um exemplo do conteúdo dos arquivos produzidos nas cancelas. Considere que no momento do processamento o estacionamento estava vazio.
Exemplo de arquivo criado pela cancela de entrada
data de entrada hora de entrada ID
08/01/2014 20:00:14 1042
08/01/2014 20:05:05 1043
08/01/2014 20:09:02 1044
08/01/2014 20:19:40 1045
[...]
Table 1: Exemplo de arquivo criado pela cancela de saída
\captionsetup{labelformat=empty}
data de saída hora de saída ID
08/01/2014 21: 15: 08 1042
08/01/2014 21: 19: 52 1044
08/01/2014 21: 43: 00 1046
08/01/2014 21: 46: 10 1047
[\ldots]
a) Escreva um programa que processe as informações desses dois arquivos, juntado os dados e produzindo um terceiro arquivo texto no formato indicado a seguir.
Table 2: Exemplo de arquivo produzido pelo processamento dos dados dos arquivos gerados pelas cancelas
\captionsetup{labelformat=empty}
ID data de entrada hora de entrada data de saída hora de saída tempo de permanência (min)
00001042 08 / 01 / 2014 20: 00: 14 08 / 01 / 2014 21: 15: 08
00001043 08 / 01 / 2014 20: 05: 05 08 / 01 / 2014 22: 15: 25
00001044 08 / 01 / 2014 20: 09: 02 08 / 01 / 2014 21: 19: 52
00001045 08 / 01 / 2014 20: 19: 40 08 / 01 / 2014 23: 45: 42
[\ldots]
b) Além de criar o arquivo processado, seu programa deverá exibir na tela algumas informações:
  • o período dos dados que foram processados (data/hora inicial e data/hora final)
  • a movimentação do estacionamento nesse período (número de carros)
  • o tempo médio de permanência dos carros no estacionamento no período (em minutos)
Segue um exemplo:
Periodo…: 08/01/2014 20:00:14 a 09/01/2014 02:04:50
Numero de carros…: 20 carros
Tempo medio de permanencia…: 78.0 minutos
Obs: Para calcular a diferença em segundos entre duas datas/horas, utilize a função date_time_difference_in_seconds definida abaixo (copie e cole no seu programa). Para que esta função possa ser utilizada, deve-se importar a biblioteca time.h.
float date_time_difference_in_seconds( int start_day, int start_month, int start_year, int start_hour, int
start_minute, int start_second, int end_day, int end_month, int end_year, int end_hour, int end_minute, int end_second
) {
    struct tm t1, t2;
    t1.tm_year = start_year-1900; t1.tm_mon = start_month; t1.tm_mday = start_day; t1.tm_hour = start_hour;
    t1.tm_min = start_minute; t1.tm_sec = start_second; t1.tm_isdst = 0; t2.tm_year = end_year-1900;
    t2.tm_mon = end_mönth; t2.tm_mday = end_day; t2.tm_hour = end_hour; t2.tm_min = end_minute;
    t2.tm_sec = end_second; t2.tm_isdst = 0;
    return ( mktime( &t2 ) - mktime( &t1 ) );
}'

Passo 1

O que o enunciado pede e quais informações são dadas:
  • Temos dois arquivos texto produzidos por duas cancelas:
    • arquivo de entrada: cada linha contém data de entrada, hora de entrada e ID (o ID é gerado pela cancela de entrada);
    • arquivo de saída: cada linha contém data de saída, hora de saída e ID (o ID é lido do tíquete na saída).
  • Os relógios estão sincronizados (portanto as datas/horas podem ser comparadas diretamente).
  • Na hora do processamento o estacionamento estava vazio (não precisamos tratar veículos que já estavam dentro antes do primeiro registro).
  • Objetivos do programa:
    1. Produzir um terceiro arquivo texto juntando (fazendo o join) os dados de entrada e saída por ID, escrevendo para cada ID: ID com zero-padding (8 dígitos no exemplo), data/hora de entrada, data/hora de saída e tempo de permanência (em minutos).
    2. Exibir na tela:
      • período dos dados processados: data/hora inicial e data/hora final (considerando todas as datas/horas lidas — tanto entradas quanto saídas);
      • movimentação do estacionamento: número de carros processados (ou seja, número de pares entrada+saída produzidos no arquivo final);
      • tempo médio de permanência dos carros no período (em minutos).
  • Observações importantes extraídas do enunciado:
    • O enunciado fornece uma função auxiliar para calcular diferença em segundos entre duas datas/horas, mas o código apresentado tem erros (variáveis com nomes errados e detalhe do mês). Devemos consertá-la antes de usar.
    • Devemos tratar parsing das datas no formato DD/MM/YYYY e horas no formato HH:MM:SS.
  • Hipóteses adotadas para a solução prática:
    • Vamos produzir no arquivo de saída apenas os registros para os quais existe tanto a entrada quanto a saída (pares completos). Registros sem par podem ser opcionalmente registrados com saída vazia, mas, por simplicidade e porque o enunciado exige “juntando os dados”, vamos gerar linhas apenas para pares encontrados.
    • O número de registros pode ser grande, mas vou propor uma solução simples e eficiente o suficiente para provas: ler todas as entradas em uma estrutura indexada por ID (usando um array + ordenação e busca binária), depois percorrer o arquivo de saídas buscando cada ID correspondente.
  • Finais: não farei suposições sobre nomes dos arquivos além de escolher nomes claros no programa (por exemplo entrada.txt, saida.txt, processado.txt).

Passo 2

Decisão do método e estruturas de dados (por que e como):
  • Método escolhido:
    • Ler todo o arquivo de entradas em memória em um vetor de structs (cada struct guarda ID e data/hora de entrada).
    • Ordenar esse vetor por ID (usar qsort) para permitir busca rápida por ID com bsearch.
    • Abrir o arquivo de saídas e para cada linha parsear ID e data/hora de saída, buscar a entrada correspondente no vetor ordenado. Se existir, criar um registro combinado (entrada+saída) e gravá-lo no arquivo de saída processado; calcular o tempo de permanência e acumular para média.
    • Enquanto lemos entradas e saídas iremos também atualizar o menor e o maior timestamp encontrados para calcular o período.
  • Por que esse método:
    • É simples de implementar com mecanismos de C padrão (qsort, bsearch) e suficiente para a maioria dos tamanhos de entrada em prova.
    • Alternativa seria usar uma tabela hash (mais trabalho para implementar em C puro) ou armazenar saídas e depois fazer merge sort; optei por ordenar entradas e usar busca binária — elegante e didático.
  • Estruturas principais:
    • typedef struct { int id; int day, month, year, hour, minute, second; } Registro;
    • Vetor dinâmico de Registro para entradas.
    • Funções auxiliares:
      • parse de linhas DD/MM/YYYY e HH:MM:SS usando sscanf.
      • comparação de Registro por id para qsort/bsearch.
      • função corrigida date_time_difference_in_seconds(...) para usar struct tm e mktime.
      • função para comparar dois timestamps (para saber min/max).
      • função para formatar ID com zero-padding de 8 dígitos ao escrever.

Passo 3

Detalhamento de parsing e manipulação de datas/horas (aulas rápidas e dicas para a prova):
  • Como ler as strings de data e hora:
    • A forma clássica em C é usar fgets para ler cada linha e depois sscanf com um formato que capture os campos numéricos:
      • sscanf(line, "%d/%d/%d %d:%d:%d %d", &day, &month, &year, &hour, &minute, &second, &id);
      • No enunciado os campos podem estar separados por espaços ou em colunas; adaptar o sscanf conforme o formato real do arquivo. Se data e hora estiverem em colunas separadas, chamar sscanf(line, "%d/%d/%d %d:%d:%d %d", ...) ainda funciona se elas estiverem com espaço entre.
    • Sempre verificar o valor de retorno de sscanf para confirmar quantos campos foram lidos.
  • Notas sobre struct tm e mktime:
    • struct tm tem campos: tm_year (anos desde 1900), tm_mon (mês de 0 a 11), tm_mday (dia do mês 1…31), tm_hour, tm_min, tm_sec.
    • Ao preencher struct tm para mktime, ajustar tm_year = year - 1900; e tm_mon = month - 1;.
    • mktime retorna time_t representando segundos desde epoch; a diferença mktime(&t2) - mktime(&t1) dá diferença em segundos (tipo double/time_t), que pode ser convertida para float.
  • Correção e reescrita da função do enunciado:
    • O enunciado tinha erros: t1.tm_mon = start_month; (deveria ser start_month - 1), variáveis com nomes errados (t2.tm_mon = end_mönth; com acento), etc. Vamos fornecer a versão correta:
      • preencher t1 e t2 corretamente, setar tm_isdst = -1 (deixar o sistema decidir, ou 0 se souber que DST não se aplica).
      • retornar (float)(difftime(mktime(&t2), mktime(&t1))) ou (mktime(&t2) - mktime(&t1)) convertido.
  • Cálculo do tempo em minutos:
    • Primeiro obter diferença em segundos com a função corrigida.
    • Converter para minutos: minutes = seconds / 60.0.
    • Para média: somar todas as durações em minutos e dividir pelo número de veículos (pares).
  • Como comparar datetimes para achar período:
    • Escrever função int cmp_datetime(Registro *a, Registro *b) que compara ano, mês, dia, hora, minuto, segundo nesta ordem. Ou converter para time_t via struct tm e comparar time_t.
    • Manter variáveis min_datetime e max_datetime atualizadas ao ler entradas e saídas (usar as funções de comparação).

Passo 4

Leitura, junção e escrita passo a passo (procedimento que você deve seguir na prova):
  1. Abrir arquivo de entradas (entrada.txt) em modo leitura; se falhar, reportar erro e sair.
  2. Ler todas as linhas do arquivo de entradas:
    • para cada linha: parsear day, month, year, hour, minute, second, id.
    • construir um Registro contendo esses campos (entrada) e guardar no vetor de entradas.
    • atualizar min_datetime e max_datetime considerando essa entrada.
  3. Fechar o arquivo de entradas.
  4. Ordenar o vetor de entradas por id com qsort.
  5. Abrir arquivo de saídas (saida.txt) em modo leitura e arquivo processado (processado.txt) em modo escrita.
  6. Inicializar contadores: num_pares = 0; soma_minutos = 0.0.
  7. Para cada linha do arquivo de saídas:
    • parsear day_out, month_out, year_out, hour_out, minute_out, second_out, id_out.
    • atualizar min_datetime e max_datetime com a saída também.
    • montar uma chave Registro com id_out e usar bsearch no vetor de entradas:
      • se encontrado:
        • calcular seconds = date_time_difference_in_seconds(entry_day,..., exit_day,...) usando a função corrigida;
        • minutes = seconds / 60.0;
        • escrever no arquivo processado.txt uma linha com:
          • ID formatado com zero-padding de 8 dígitos (usar fprintf(out, "%08d ...", id)),
          • data/hora entrada e saída no mesmo formato do enunciado,
          • (opcionalmente) o tempo em minutos (o enunciado pedia tempo de permanência (min) na tabela de exemplo — preserve esse campo).
        • incrementar num_pares++ e soma_minutos += minutes.
      • se não encontrado: ignorar (ou registrar em log se quiser depurar).
  8. Ao terminar, fechar arquivos.
  9. Calcular tempo médio: se num_pares > 0, media = soma_minutos / num_pares; senão media = 0.
  10. Exibir na tela:
    • período: imprimir min_datetime e max_datetime no formato DD/MM/YYYY HH:MM:SS.
    • número de carros: num_pares.
    • tempo médio de permanência: media (com uma casa decimal, por exemplo).
  11. Encerrar.

Passo 5

Tratamento de casos extremos e boas práticas (dicas para a prova):
  • Se uma saída aparece antes da entrada correspondente (por erro de dados), a função de diferença em segundos pode retornar valor negativo; você pode:
    • optar por ignorar pares onde saída < entrada e emitir aviso;
    • ou considerar o valor absoluto se o enunciado permitir. Eu recomendo detectar e emitir aviso (ou tratar como dado sujo).
  • Verifique sempre o retorno de fopen, fgets, sscanf, malloc/realloc.
  • Para arquivos grandes: ler entradas e saídas em streaming com uma tabela hash é mais eficiente; em prova, explicado esse método e implementado o método por ordenação é suficiente.
  • Formatação do ID: usar "%08d" para zero-padding até 8 dígitos.
  • Ao usar mktime, inicialize todos os campos de struct tm com 0 antes de preencher, para evitar lixo em campos.

Passo 6

Exemplo de comportamento final esperado (com base no enunciado):
  • O arquivo processado.txt terá uma linha por veículo pareado, por exemplo:
    • 00001042 08/01/2014 20:00:14 08/01/2014 21:15:08 75
      (onde o último campo 75 seria tempo de permanência em minutos arredondado ou truncado conforme sua escolha).
  • Na tela será impresso algo como:
    • Periodo.....................: 08/01/2014 20:00:14 a 09/01/2014 02:04:50
    • Numero de carros............: 20 carros
    • Tempo medio de permanencia..: 78.0 minutos

Passo 7

Apresentação do programa completo em C (use este código na prova, copie e cole, compile com gcc):

Resposta

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef struct {
    int id;
    int day, month, year;
    int hour, minute, second;
} Registro;

int cmp_registro_by_id(const void *a, const void *b) {
    const Registro *ra = (const Registro *)a;
    const Registro *rb = (const Registro *)b;
    if (ra->id < rb->id) return -1;
    if (ra->id > rb->id) return 1;
    return 0;
}

int cmp_datetime(const Registro *a, const Registro *b) {
    if (a->year != b->year) return (a->year < b->year) ? -1 : 1;
    if (a->month != b->month) return (a->month < b->month) ? -1 : 1;
    if (a->day != b->day) return (a->day < b->day) ? -1 : 1;
    if (a->hour != b->hour) return (a->hour < b->hour) ? -1 : 1;
    if (a->minute != b->minute) return (a->minute < b->minute) ? -1 : 1;
    if (a->second != b->second) return (a->second < b->second) ? -1 : 1;
    return 0;
}

float date_time_difference_in_seconds(int start_day, int start_month, int start_year, int start_hour, int
                      start_minute, int start_second, int end_day, int end_month, int end_year, int end_hour, int end_minute, int end_second) {
    struct tm t1 = {0}, t2 = {0};
    t1.tm_year = start_year - 1900;
    t1.tm_mon = start_month - 1;
    t1.tm_mday = start_day;
    t1.tm_hour = start_hour;
    t1.tm_min = start_minute;
    t1.tm_sec = start_second;
    t1.tm_isdst = -1;
    t2.tm_year = end_year - 1900;
    t2.tm_mon = end_month - 1;
    t2.tm_mday = end_day;
    t2.tm_hour = end_hour;
    t2.tm_min = end_minute;
    t2.tm_sec = end_second;
    t2.tm_isdst = -1;
    time_t tt1 = mktime(&t1);
    time_t tt2 = mktime(&t2);
    return (float)difftime(tt2, tt1);
}

void format_datetime(char *buf, size_t bufsize, const Registro *r) {
    snprintf(buf, bufsize, "%02d/%02d/%04d %02d:%02d:%02d",
             r->day, r->month, r->year, r->hour, r->minute, r->second);
}

int main() {
    const char *file_entrada = "entrada.txt";
    const char *file_saida = "saida.txt";
    const char *file_processado = "processado.txt";

    FILE *fin = fopen(file_entrada, "r");
    if (!fin) {
        fprintf(stderr, "Erro ao abrir %s\n", file_entrada);
        return 1;
    }

    // Ler entradas dinamicamente
    Registro *entradas = NULL;
    size_t cap = 0, n_entradas = 0;
    char line[256];

    Registro min_dt, max_dt;
    int first_dt = 1;

    while (fgets(line, sizeof(line), fin)) {
        // Tentar ler linha no formato: DD/MM/YYYY HH:MM:SS ID
        int d, m, y, hh, mm, ss, id;
        int read = sscanf(line, " %d/%d/%d %d:%d:%d %d", &d, &m, &y, &hh, &mm, &ss, &id);
        if (read < 7) {
            // Tentar outra possibilidade: data e hora separados por tabs/espacos e ID em outra coluna
            // Exemplo: "08/01/2014    20:00:14    1042"
            read = sscanf(line, " %d/%d/%d %d:%d:%d %d", &d, &m, &y, &hh, &mm, &ss, &id);
        }
        if (read >= 7) {
            if (n_entradas == cap) {
                cap = (cap == 0) ? 128 : cap * 2;
                entradas = realloc(entradas, cap * sizeof(Registro));
                if (!entradas) { fprintf(stderr, "Memoria insuficiente\n"); fclose(fin); return 1; }
            }
            entradas[n_entradas].id = id;
            entradas[n_entradas].day = d;
            entradas[n_entradas].month = m;
            entradas[n_entradas].year = y;
            entradas[n_entradas].hour = hh;
            entradas[n_entradas].minute = mm;
            entradas[n_entradas].second = ss;
            // atualizar min/max
            if (first_dt) {
                min_dt = entradas[n_entradas];
                max_dt = entradas[n_entradas];
                first_dt = 0;
            } else {
                if (cmp_datetime(&entradas[n_entradas], &min_dt) < 0) min_dt = entradas[n_entradas];
                if (cmp_datetime(&entradas[n_entradas], &max_dt) > 0) max_dt = entradas[n_entradas];
            }
            n_entradas++;
        }
    }
    fclose(fin);

    // Ordenar entradas por ID
    if (n_entradas > 0) qsort(entradas, n_entradas, sizeof(Registro), cmp_registro_by_id);

    FILE *fout = fopen(file_processado, "w");
    if (!fout) {
        fprintf(stderr, "Erro ao abrir %s para escrita\n", file_processado);
        free(entradas);
        return 1;
    }

    FILE *fs = fopen(file_saida, "r");
    if (!fs) {
        fprintf(stderr, "Erro ao abrir %s\n", file_saida);
        free(entradas);
        fclose(fout);
        return 1;
    }

    size_t num_pares = 0;
    double soma_minutos = 0.0;

    while (fgets(line, sizeof(line), fs)) {
        int d, m, y, hh, mm, ss, id;
        int read = sscanf(line, " %d/%d/%d %d:%d:%d %d", &d, &m, &y, &hh, &mm, &ss, &id);
        if (read < 7) {
            // Tentar formato com espaços estranhos, ou com parênteses; tentar extrair números de qualquer jeito
            read = sscanf(line, " %d/%d/%d %d:%d:%d %d", &d, &m, &y, &hh, &mm, &ss, &id);
        }
        if (read >= 7) {
            Registro chave;
            chave.id = id;
            Registro *found = bsearch(&chave, entradas, n_entradas, sizeof(Registro), cmp_registro_by_id);
            // atualizar min/max com a saída
            Registro reg_out;
            reg_out.id = id; reg_out.day = d; reg_out.month = m; reg_out.year = y;
            reg_out.hour = hh; reg_out.minute = mm; reg_out.second = ss;
            if (first_dt) {
                min_dt = reg_out;
                max_dt = reg_out;
                first_dt = 0;
            } else {
                if (cmp_datetime(&reg_out, &min_dt) < 0) min_dt = reg_out;
                if (cmp_datetime(&reg_out, &max_dt) > 0) max_dt = reg_out;
            }

            if (found) {
                float secs = date_time_difference_in_seconds(found->day, found->month, found->year, found->hour, found->minute, found->second,
                                                             reg_out.day, reg_out.month, reg_out.year, reg_out.hour, reg_out.minute, reg_out.second);
                double minutos = secs / 60.0;
                // Se preferir, pode ignorar pares com tempo negativo:
                // if (secs < 0) continue;
                // Gravar no arquivo processado no formato pedido
                char buf_ent[64], buf_sai[64];
                format_datetime(buf_ent, sizeof(buf_ent), found);
                format_datetime(buf_sai, sizeof(buf_sai), &reg_out);
                // Exemplo de linha: ID(8 dígitos) data_ent hora_ent data_sai hora_sai tempo_min (tempo em minutos com 1 casa decimal)
                fprintf(fout, "%08d %s %s %.1f\n", found->id, buf_ent, buf_sai, minutos);

                num_pares++;
                soma_minutos += minutos;
            } else {
                // saída sem entrada correspondente: ignorar (ou logar)
            }
        }
    }

    fclose(fs);
    fclose(fout);

    free(entradas);

    double media = (num_pares > 0) ? (soma_minutos / (double)num_pares) : 0.0;

    // Imprimir resultado conforme exemplo
    char buf_min[64], buf_max[64];
    format_datetime(buf_min, sizeof(buf_min), &min_dt);
    format_datetime(buf_max, sizeof(buf_max), &max_dt);

    printf("Periodo.....................: %s a %s\n", buf_min, buf_max);
    printf("Numero de carros............: %zu carros\n", num_pares);
    printf("Tempo medio de permanencia..: %.1f minutos\n", media);

    return 0;
}

Exercícios relacionados