Registos em Ficheiros - Estruturas Pedro Barahona DI/FCT/UNL Introdução aos Computadores e à Programação 2º Semestre 2006/2007 4 Maio 2007 Registos em Ficheiros - Estruturas 1 Registos em Ficheiros • Muita informação alfanumérica está registada em ficheiros, na forma de “registos”. Por exemplo, numa base de dados da empresa, são mantidos ficheiros com informação sobre os empregados da empresa. • Muitas aplicações (de gestão) consistem em ler ficheiros e criar outros com a informação devidamente processada. Por exemplo: ler um ficheiro de empregados e escrever outro, apenas com os empregados com vencimento superior a 1000 €. cod 610 825 316 34 723 4 Maio 2007 nome Paulo Fernandes Lopes Pedro Vieira Marta Costa Martins Rui Vasco Pereira Jorge Barata vencimento data 2341.36 989.24 1389.17 5310.32 767.26 15/04/1996 25/06/1999 05/01/1992 15/04/1996 03/09/2002 Registos em Ficheiros - Estruturas 2 Registos em Ficheiros • A primeira questão a resolver consiste no tratamento dos caracteres brancos em sequências de caracteres. Isto porque há duas formas típicas de armazenamento dessas sequências: – Comprimento Fixo: As sequências têm sempre o mesmo número de caracteres (sendo usados espaços se necessário); – Comprimento Variável: As sequências têm o número de caracteres necessários, sendo necessários caracteres separadores, tipicamente tabs (horizontais). cod 610 825 316 34 723 4 Maio 2007 nome Paulo Fernandes Lopes Pedro Vieira Marta Costa Martins Rui Vasco Pereira Jorge Barata vencimento data 2341.36 989.24 1389.17 5310.32 767.26 15/04/1996 25/06/1999 05/01/1992 15/04/1996 03/09/2002 Registos em Ficheiros - Estruturas 3 Registos em Ficheiros • Por exemplo, o nome “Pedro Vieira”, no ficheiro abaixo, pode ser codificado – Comprimento Fixo: Com 5+1+6 = 12 caracteres (incluindo o espaço), mais 13 espaços, para permitir sequências de comprimento 25, que podem armazenar nomes com até 25 caracteres (incluindo espaços); – Comprimento Variável: Apenas com os 12 caracteres necessários, sendo separado do vencimento por um tab horizontal (‘\t’). cod 610 825 316 34 723 4 Maio 2007 nome Paulo Fernandes Lopes Pedro Vieira Marta Costa Martins Rui Vasco Pereira Jorge Barata vencimento data 2341.36 989.24 1389.17 5310.32 767.26 15/04/1996 25/06/1999 05/01/1992 15/04/1996 03/09/2002 Registos em Ficheiros - Estruturas 4 Registos em Ficheiros • Em Octave (e em C) os dois tipos de codificação requerem instruções de leitura padronizada (com templates) diferentes. – Comprimento Fixo: Utiliza-se um template “%nc” em que n é o número de caracteres a ler; – Comprimento Variável: Utiliza-se um template “%s”; • Neste último caso, levanta-se um problema: O template ‘%s’, não lê nem caracteres brancos nem espaços. Assim, o nome “Pedro Vieira” seria lido não como 1, mas sim como 2 sequências. Em geral, ter-se-ia de conhecer o número de nomes (próprios, apelidos e partículas de ligação). • Isto pode ser evitado com o uso de espaços especiais (“non break spaces” ASCII 160), que são considerados como quaisquer outros caracteres, mas impressos como espaços. 4 Maio 2007 Registos em Ficheiros - Estruturas 5 Leitura de Ficheiros • Caso seja necessário, pode converter-se um ficheiro noutro, convertendo-se todos os espaços em espaços especiais, com a função abaixo: function x = rem_sp(f_in_name, f_out_name); [f_in, msg] = fopen(f_in_name , "r"); [f_aux,msg] = fopen(f_out_name, "w"); [ch, count] = fscanf(f_in,"%1c","C"); while !feof(f_in) if ch == " " ch = setstr(160); endif; fprintf(f_aux, "%1c", ch); [ch, count] = fscanf(f_in,"%1c","C"); endwhile; fclose(f_in); fclose(f_aux); endfunction; 4 Maio 2007 Registos em Ficheiros - Estruturas 6 Leitura de Ficheiros • Comprimento Fixo: – Cada linha do ficheiro, assumindo-se que um nome é guardado com 25 caracteres, pode ser lida com as seguintes instruções de leitura padronizada, fscanf [cod, count] [nome, count] [venc, count] [data,count] = = = = fscanf(fid,"%i","C"); fscanf(fid,"%25c","C"); fscanf(fid,"%f","C"); fscanf(fid,"%s","C"); ou numa só instrução [cod,nome,venc,data,count]=fscanf(fid,"%i%25c%f%s","C"); 610 4 Maio 2007 Paulo Fernandes Lopes 2341.36 Registos em Ficheiros - Estruturas 15/04/1996 7 Leitura de Ficheiros • Comprimento Variável: – Neste caso, quer o nome quer a data podem ser lidos com o template “%s” da instrução fscanf, podendo o registo de um empregado ser lido quer na forma : [cod, count] [nome, count] [venc, count] [data,count] = = = = fscanf(fid,"%i","C"); fscanf(fid,"%s","C"); fscanf(fid,"%f","C"); fscanf(fid,"%s","C"); quer numa só instrução [cod,nome,venc,data,count] = fscanf(fid,"%i%s%f%s","C"); 610 4 Maio 2007 Paulo Fernandes Lopes 2341.36 Registos em Ficheiros - Estruturas 15/04/1996 8 Escrita de Ficheiros • Comprimento variável: – Neste caso o registo de um empregado pode ser escrito como fprintf(fid, fprintf(fid, fprintf(fid, fprintf(fid, "%i\t", cod); "%s\t", nome); "%7.2f\t", venc); "%s\n", data); ou numa só instrução, como anteriormente. Notar agora que 1. Após cada campo, deve ser escrito o tab (‘\t’) de separação, excepto no último campo, após o que se escreve o caracter (‘\n’) 2. 610 4 Maio 2007 Alguns templates podem ser fixos, (por exemplo, "%7.2f\t”) para evitar as convenções por omissão do Octave. Paulo Fernandes Lopes 2341.36 Registos em Ficheiros - Estruturas 15/04/1996 9 Escrita de Ficheiros • Comprimento fixo: – A escrita de ficheiros depende igualmente do formato utilizado para as strings. Em comprimento fixo, o registo pode ser escrito como fprintf(fid, fprintf(fid, fprintf(fid, fprintf(fid, fprintf(fid, "%3i", cod); "%-25s", nome); "%7.2f", venc); "%10s", data); "%c", “\n”); ou numa só instrução, como anteriormente. Notar ainda que 1. O sinal – (em %-25s) justifica, à esquerda, o campo nome. 2. Após o último campo deve ser escrito um caracter (‘\n’), para mudança de linha 610Paulo Fernandes Lopes 4 Maio 2007 2341.3615/04/1996 Registos em Ficheiros - Estruturas 10 Selecção de Registos • Podemos agora abordar o problema inicialmente colocado: 1. Ler um ficheiro de empregados; e 2. Escrever outro, apenas com os empregados com vencimento superior a 1000 euros. • Naturalmente o programa dependerá de os ficheiros serem lidos e escritos em formato fixo ou variável. • Assumiremos que esta diferença apenas se reflectirá no campo “nome”, já que o campo “data” não contem espaços e pode ser lido em formato cadeia (“%s”) sem problema. 4 Maio 2007 Registos em Ficheiros - Estruturas 11 Selecção de Registos • Eis a versão para formato fixo : [f_in, msg_in ] = fopen("empresa_in_fix.txt", "r"); [f_out, msg_out] = fopen("empresa_out_fix.txt", "w"); [cod,nome,venc,data,ct] = fscanf(f_in,"%i%25c%f%s","C"); while !feof(f_in) if venc > 1000 fprintf(f_out,"%3i%-25s%7.2f%12s\n", cod,nome,venc,data); printf( "%3i%-25s%7.2f%12s\n", cod,nome,venc,data); endif; [cod,nome,venc,data,ct] = fscanf(f_in,"%i%25c%f%s","C"); endwhile; fclose(f_in); fclose(f_out); 4 Maio 2007 Registos em Ficheiros - Estruturas 12 Selecção de Registos • E a versão para formato variável : rem_sp("empresa_in_var.txt", "empresa_aux_var.txt"); [f_aux, msg] = fopen("empresa_aux_var.txt", "r"); [f_out, msg] = fopen("empresa_out_var.txt", "w"); [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); while !feof(f_aux) if venc > 1000 fprintf(f_out, "%i\t%s\t%7.2f\t%s\n", cod,nome,venc,data); printf( "%i\t%s\t%7.2f\t%s\n", cod,nome,venc,data); endif; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); endwhile; fclose(f_aux); fclose(f_out); 4 Maio 2007 Registos em Ficheiros - Estruturas 13 Processamento de Registos • Podemos agora considerar outro tipo de processamento de informação, que não envolve necessariamente a escrita de novos ficheiros, correspondente a: – Cálculo de totais e médias (de vencimentos, por exemplo) – Determinação de máximos e mínimos (de vencimentos, ou antiguidades) • Vamos ilustrar estes problemas com programas para determinação dos vencimentos totais e médios dos empregados da empresa, bem como da determinação do empregado mais antigo. • Para ambos os problemas, apenas são apresentadas as versões para formato variável. • De notar ainda a instrução printf, que permite escrever no terminal mensagens formatadas (com os formatos usados em ficheiros). 4 Maio 2007 Registos em Ficheiros - Estruturas 14 Processamento de Registos • O tratamento de vencimentos utiliza um contador (variável i) de registos lidos e uma variável (total) para determinação do total dos vencimentos (sendo a média igual à razão entre total e i). rem_sp("empresa_in_var.txt", "empresa_aux_var.txt"); [f_aux, msg] = fopen("empresa_aux_var.txt", "r"); i = 0; total = 0; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); while !feof(f_aux) i = i+1; total = total+venc; [cod,nome,venc,data,count] = fscanf(f_aux,"%i%s%f%s","C"); endwhile; printf("o total dos vencimentos é de %7.2f \n", total); printf("a média dos vencimentos é de %7.2f \n",total/i); fclose(f_aux); fclose(f_out); 4 Maio 2007 Registos em Ficheiros - Estruturas 15 Processamento de Registos • O tratamento de datas, implica reconhecer quando uma data é anterior a outra, o que é feito com a função “data_comp” que compara duas datas, sendo armazenada a data menor rem_sp("empresa_in_var.txt", "empresa_aux_var.txt"); [f_aux, msg] = fopen("empresa_aux_var.txt", "r"); data_menor =“01/01/2100”; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); while !feof(f_aux) if data_comp(data,data_menor) = -1 data_menor = data; endif; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); endwhile; printf("a entrada mais antiga foi em %s\n",data_menor); fclose(f_aux); 4 Maio 2007 Registos em Ficheiros - Estruturas 16 Comparação de Datas • A comparação de datas é feita através da comparação dos anos, meses e dias, das duas datas • Naturalmente, há que ter o cuidado de converter inicialmente as cadeias de caracteres em números (com a função str2num). function d = data_comp(data1,data2); % data no formato dd/mm/aaaa ano1 = str2num(substr(data1,7,4)); ano2 = str2num(substr(data2,7,4)); mes1 = str2num(substr(data1,4,2)); mes2 = str2num(substr(data2,4,2)); dia1 = str2num(substr(data1,1,2)); dia2 = str2num(substr(data2,1,2)); d = d_comp(ano1, mes1, dia1, ano2, mes2, dia2); endfunction 4 Maio 2007 Registos em Ficheiros - Estruturas 17 Comparação de Datas • Uma vez obtidos os anos, dias e meses as datas podem ser comparadas, comparando-se sucessivamente os anos, e se necessário os meses e dias). function d = d_comp(ano1, if ano1 < ano2 d elseif ano1 > ano2 d elseif mes1 < mes2 d elseif mes1 > mes2 d elseif dia1 < dia2 d elseif dia1 > dia2 d else d endif endfunction 4 Maio 2007 mes1, dia1, ano2, mes2, dia2); = -1; = 1; = -1; = 1; = -1; = 1; = 0; Registos em Ficheiros - Estruturas 18 Processamento de Registos • No caso do empregado mais antigo, é interessante informar não só quando ele entrou ao serviço (isto é , a data), mas também que é ele (isto é, o seu nome) . • Este problema pode ser resolvido com uma pequena adaptação do código, guardando não só a data mais antiga como o nome rem_sp("empresa_in_var.txt", "empresa_aux_var.txt"); [f_aux, msg] = fopen("empresa_aux_var.txt", "r"); data_menor =“01/01/2100”; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); while !feof(f_aux) if data_comp(data,data_menor) == -1 data_menor = data; antigo = nome; endif; [cod,nome, venc, data, count] = fscanf(f_aux,"%i%s%f%s","C"); endwhile; printf("o empregado mais antigo é %s \n", antigo); printf("com data de entrada %s \n", data_menor); fclose(f_aux); 4 Maio 2007 Registos em Ficheiros - Estruturas 19 Estruturas e Listas • Todos estes programas obrigam, cada vez que se pretende responder a uma questão : 1. A ler registos de um ficheiro; 2. A processar cada um dios registos, isoladamente; e, eventualmente 3. A escrever os registos num outro ficheiro • No entanto, a leitura e escrita de ficheiros, mesmo em “discos rápidos” é tipicamente milhares de vezes mais lenta que a leitura a partir de dados em memória. • Haverá pois vantagem em copiar um ficheiro de registos para memória e passar a responder às questões a partir de aí. • Veremos como responder a esta questão em geral e, no caso do Octave, como tal poderá ser feitos com estruturas e listas. 4 Maio 2007 Registos em Ficheiros - Estruturas 20 Estruturas • Embora Vectores e Matrizes sejam muito úteis quando os dados são todos do mesmo tipo (no Octave, de qualquer tipo numérico), em muitos casos, a informação que se pretende agrupar com um só identificador não é do mesmo tipo, como é o caso dos registos considerados anteriormente. • Por exemplo, um empregado duma empresa pode ser associado à seguinte informação – Um código (um número?) – Um nome (uma cadeia de caracteres) – Um vencimento (um decimal) – Uma data de entrada (uma cadeia, ou 3 campos numéricos, para o dia, mês e ano) cod nome 610 Paulo Fernandes Lopes 4 Maio 2007 venc 2341.36 data 15/04/1996 Registos em Ficheiros - Estruturas 21 Estruturas • As várias linguagens de programação permitem o agrupamento destes dados heterogéneos, com um mesmo identificador de uma forma variada (records no Pascal, Struct em C, ...) • O Octave adopta uma designação semelhante à do C, denominando estes agrupamentos como estruturas. • Consideremos pois o caso do empregado abaixo, em que gostaríamos de agregar toda a informação numa única variável, do tipo estrutura, que denotaremos como emp_610. cod nome 610 Paulo Fernandes Lopes 4 Maio 2007 venc 2341.36 data 15/04/1996 Registos em Ficheiros - Estruturas 22 Estruturas • Uma vez definidos os nomes dos campos da estrutura, podemos atribuir-lhe os valores pretendidos. • O acesso a um campo da estrutura é feito fazendo suceder ao nome da estrutura o nome do campo pretendido, separado por um ponto (‘.’). • Por exemplo, a atribuição dos 4 valores dos campos pode ser feita pelas seguintes atribuições: emp_610.cod = 610; emp_610.nome = “Paulo Fernandes Lopes”; emp_610.venc = 2341.36; emp_610.data=“15/04/1996”; emp_610 = 4 Maio 2007 cod nome 610 Paulo Fernandes Lopes venc 2341.36 Registos em Ficheiros - Estruturas data 15/04/1996 23 Estruturas • Uma vez agrupados os vários items de informação numa só variável do tipo estrutura, podemos referir alguns campos depois de verificar outros. • Por exemplo, dados vários empregados com o tipo referido, indicar qual o nome dos que ganham mais de 1000 euros. • Na sintaxe do Octave, tal poderia ser feito através da instrução condicional if emp_610.venc > 1000 then disp(emp_610.nome) endif • No entanto este tipo de processamento só é verdadeiramente útil se tivermos a possibilidade de aceder a todos os empregados de uma forma genérica. 4 Maio 2007 Registos em Ficheiros - Estruturas 24 Tabelas • Por exemplo, se tivessemos uma tabela com várias linhas, com códigos na primeira coluna e vencimentos na 2ª coluna, poderíamos apresentar os códigos dos empregados com vencimento superior a 1000 euros através da seguinte instrução iterativa: 1 2 1 610 2341.36 for i = 1:n 2 825 989.24 if tabela(i,2) > 1000 disp(tabela(i,1)) 3 316 1389.17 endif 4 34 5310.32 endfor; 5 723 767.26 ... ... ... Por analogia, o que é necessário é poder aceder a uma sequência de (1 a n) estruturas do tipo da do empregado. • Na maioria das linguagens de programação, essa analogia é imediata, já que se podem especificar vectores de estruturas. 4 Maio 2007 Registos em Ficheiros - Estruturas 25 Vectores de Estruturas • • Nestas linguagens, poderíamos representar o conjunto de empregados através de um vector, emps, em que cada elemento é uma estrutura (de empregado) com os campos definidos como anteriormente. ind cod 1 2 3 4 5 610 825 316 34 723 nome Paulo Fernandes Lopes Pedro Vieira Marta Costa Martins Rui Vasco Pereira Jorge Barata vencimento data 2341.36 989.24 1389.17 5310.32 767.26 15/04/1996 25/06/1999 05/01/1992 15/04/1996 03/09/2002 Agora, para obter os códigos dos empregados com vencimento superior a 1000 euros bastaria usar uma seguinte instrução iterativa, análoga à anterior for i = 1:n if emps(i).vencimento > 1000 disp(emps(i)) endif endfor; 4 Maio 2007 for i = 1:n if tabela(i,2) > 1000 disp(tabela(i,1)) endif endfor; Registos em Ficheiros - Estruturas 26 Listas • Em Octave, não existe a possibilidade de definir vectores de estruturas, já que os vectores são numéricos. • O Octave no entanto, disponibiliza uma estrutura de dados, a lista, que permite organizar um conjunto de elementos em memória, podendo esses elelemntos ter um tipo qualquer, numérico ou não. • Antecipando, poderá ser usada, em Octave, o conjunto de empregados pode ser representada por uma lista, que também designaremos por emps, e que permite o acesso a um seu elemento de uma forma “parecida”, nomeadamente for i = 1:n if nth(emps,i).vencimento > 1000 disp(nth(emps,i)) endif endfor; 4 Maio 2007 Registos em Ficheiros - Estruturas 27