João Araujo
Dr. en Informatique, Université de Versailles, França.

Retornar para Primeira Prova - 2005-1

Resolução da Primeira Prova de 2005-1

Algoritmo de solução

Para resolver este problema vamos começar por criar um algoritmo. Ele é bem simples:

Exemplo 1.1. Algoritmo de Solução

/*
  Enquanto não for fim de arquivo {
    Lê linha;
    Soma linha;
    Criptografa linha;
    Imprime linha criptografada;
  }
  Imprime soma;
*/

Vamos começar pelo mais fácil. Do livro temos a estrutura básica do programa no item 1.9 e temos a função lelinha() (getline na versão em inglês). Também é dito no enunciado que os caracteres de nova linha e tabulação devem ser tratados como espaço em branco. De acordo com o item 1.9 podemos modificar nosso algoritmo inicial:

Exemplo 1.2. Algoritmo melhorado

/*
  Solução
  Enquanto (há outra linha) {
    filtra tabulações e novas linhas;
    Soma linha;
    Criptografa linha;
    Imprime linha criptografada;
  }
  Imprime soma;
*/

Primeira versão

Do enunciado do texto, podemos inferir algumas constantes como MAXLINHA e MAXPRINT, nosso cabeçalho seria este:

Exemplo 1.3. Primeiras definições

#include <sdtio.h> #define MAXLINHA 100 #define MAXPRINT 80

A criação de variáveis é bem simples: do item 1.9 tiramos “tam” e a string “linha”. Também criamos a variável “soma”, para guardar a soma de todas as letras do texto. Comecemos a programar (versão 1):

Exemplo 1.4. Primeira versão

#include <stdio.h>
#include <ctype.h>
#define MAXLINHA 100
#define MAXPRINT 80
 
int lelinha(char s[],int lim);
void imprime(char s[],int lim);
void filtraespacos(char s[]);
void criptografa(char s[]);
int sumchar(char s[]); 
 
int main()
{
  int tam, soma=0;
  char linha[MAXLINHA];
 
  while ((tam=lelinha(linha,MAXLINHA))>0)
  {
    filtraespacos(linha);
    soma+=sumchar(linha);
    criptografa (linha);
    imprime (linha, MAXPRINT);
  }
  // printf("\nSoma é %d\n",soma);
}
 
int lelinha(char s[],int lim){
}
void imprime(char s[],int lim){
}
void filtraespacos(char s[]){
}
void criptografa(char s[]){
}
int sumchar(char s[]){
}

Primeiro ponto: Versões 2 e 3

Este programa ainda não faz nada de útil. Vamos completar o que falta. Primeiro lelinha() que copiamos diretamente do item 1.9 do livro (versão 2) .

Exemplo 1.5. lelinha()

int lelinha(char s[],int lim)
{
  int c,i;
 
  for (i=0; i <lim-1 && (c=getchar())!=EOF && c!='\n';++i)
    s[i]=c;
  if (c=='\n')
   {
    s[i]=c;
    ++i;
   }
  s[i]='\0';
  return i;
}

Vamos garantir o primeiro ponto: tratamento correto para linhas com mais de 100 caracteres. Para isso, basta tratar este caso em main() e em lelinha(). Este caso é semelhante ao exercício dado em sala de aula (ex 1.16). Como o enunciado não diz se devemos parar o programa ou não, escolhemos continuar o processamento após a mensagem de erro.

Primeiro modificamos lelinha() para ele não parar de contar após ter atingido seu limite. Usaremos uma variável j.

Exemplo 1.6. lelinha() modificado

//Função para ler linha até encontrar caracter de nova linha ou fim de arquivo. 
//Versão modificada para continuar lendo após final de espaço em s[]
int lelinha(char s[],int lim)
{
  int c,i;
 
  for (i=0; (c=getchar())!=EOF && c!='\n';++i)
    if (i<lim-1) s[i]=c;
  if (i<lim-1 && c=='\n')
   {
    s[i]=c;
    ++i;
   }
  s[i]='\0';
  return i;
}

Depois modificamos main() (versão 3) .

Exemplo 1.7. main() modificado para detectar erro de excesso de caracteres

int main()
{
  int tam, soma=0;
  char linha[MAXLINHA];
 
  while ((tam=lelinha(linha,MAXLINHA))>0)
  { 
    if (tam>=MAXLINHA )
       printf("\nErro: linha maior que o permitido\n"); 
    filtraespacos(linha);
    soma+=sumchar(linha);
    criptografa (linha);
    imprime (linha, MAXPRINT);
  }
  printf("\nSoma é %d\n",soma);
}

Esta versão já faz algo: Lê do teclado até encontrar o final do arquivo e detecta erro de excesso de caracteres na entrada. Segundo ponto: saída formatada

Agora, vamos garantir mais 1 ponto na prova. Fazemos o mais fácil: imprimir em 80 letras por linha (versão 4) .

Exemplo 1.8. Função imprime()

void imprime(char s[], int lim)
{ 
  int i=1;
  int j=0;
  while( s[j]!='\0')
  {
    printf((i%lim)? "%c":"%c\n",s[j++]);
    i++;
  }
}

Começamos a variável “i” com o valor 1 para que ela conte exatamente “lim” caracteres antes de escrever a nova linha. Se ela começasse com 0, no primeiro teste da linha o resto seria zero e seria escrito a nova linha.

Vamos compilar e testar. Para isso, é melhor reduzir a constante MAXPRINT para um valor mais baixo, digamos 20.

Um teste simples mostra que funciona mas… e se tentarmos com várias linhas? Oops.. o ponto ainda não é nosso.

neste caso vemos que a cada nova linha ele recomeça a contar de zero a posição na linha. Devemos usar uma variável que preserve seu valor entre as chamadas. Poderíamos usar uma variável global, assim (versão 5) :

Exemplo 1.9. imprime() melhorado

int posicaoPrint=1;
void imprime(char s[], int lim)
{ 
  int j=0;
  while( s[j]!='\0')
  {
    printf((posicaoPrint%lim)? "%c":"%c\n",s[j++]);
    posicaoPrint++;
  }
}

Extraindo tabulações e novas linhas

O resultado ainda não é satisfatório, pois temos os caracteres de final de linha em todas as linhas. Então vamos melhorar nosso programa eliminando os caracteres de final de linha e tabulação com a função filtraespacos(). Ela também é bem simples: (versão 6)

Exemplo 1.10. filtraespacos()

void filtraespacos(char s[])
{
   int i;
   for (i=0; s[i]!='\0'; i++)
    if (s[i]=='\n' || s[i]=='\t')
       s[i]=' ';
}

Mais um ponto: soma de caracteres

Partimos para mais um ponto: soma correta dos caracteres. Como já eliminamos os caracteres de tabulação e nova linha, esta rotina será assim (versão 7) :

Exemplo 1.11. sumchar()

int sumchar(char s[])
{
   int i, soma=0;
   for (i=0;s[i]!='\0';i++)
     soma+=(int)s[i];
   return soma;
}

Finalmente a criptografia!

Agora chegamos ao mais difícil: a criptografia do texto.

Vamos começar definindo algumas strings. Primeiro, vamos usar a string com a matrícula e seu tamanho:

Exemplo 1.12. String da matrícula

char mat[]="199112340567";
int tamMat=12;

A segunda string vai ser essencial no processo de criptografia. Escolhemos o meio mais fácil, ou seja, uma que permita fazer apenas criptografia em avanço, para garantir um ponto:

Exemplo 1.13. string letras[]

char letras[]=" abcdefghijklmnopqrstuvwxyz"; 
int tamLetras=27;

Para calcular o índice que vai ser usado, para letras entre 'a' e 'z', usamos a expressão:

novaletra= letra[pos_em_letras + deslocamento]

O deslocamento é calculado usando a string mat. Para isso precisamos de um contador que vai manter o índice de mat entre as várias linhas. Como este valor deve ficar entre 0 e 11, podemos usar a fórmula: pos%tamanho_de_mat.

O tamanho da matrícula é 12. Criamos a variável tamMat, que guarda este valor.

deslocamento= mat[j%tamMat].

A posição em letras é dada pela fórmula:

s[i]-'a'+1 (O valor 1 é por causa do caracter espaço inicial).

Juntando tudo, temos:

s[i]= letras[(s[i]-'a'+1+(mat[j%tamMat]-'0'))%tamLetras]

Veja que usamos o resto da expressão pelo tamanho da string letras de modo que se o valor ultrapassar 26, o índice seja calculado corretamente, voltando para o início da string.

Façamos um teste. Se a letra a ser convertida for 'b' e o dígito da matrícula for '2', s[i]-'a'1. Somando 1, temos 2.

Na matrícula '2'-'0'2 e o resultado final é 4. então o resultado será a letra de índice 4 em letras[]. Isto dá 'd'. O resultado está correto.

Peguemos o final da string. Com a letra sendo 'z'. s[i]-'a'25. Mais 1, dá 26, mais 2(de mat[j%tamMat]-'0'), dá 28 cujo resto por 271. A letra com índice 1 é 'a' e a criptografia foi correta.

Isto ainda não contempla o espaço em branco, que deve ser tratado como um caso à parte. Para o espaço, já sabemos que seu índice na string letras[] é 0. O cálculo fica:

s[i]=letras[mat[j%tamMat]-'0']

Nestes casos, j deve manter seu valor entre várias chamadas. Ele será feito global com o nome de 'pos'. (versão 8)

Exemplo 1.14. primeira versão de criptografa()

int pos=0;
void criptografa(char s[])
{
  char mat[]="199112340567";
  char letras[]=" abcdefghijklmnopqrstuvwxyz"; 
  int tamLetras=27;
  int tamMat=12;
  int i=0;
 
  while (s[i]!='\0'){
    if (s[i] >='a' && s[i]<='z')
       s[i]= letras[(s[i]-'a'+1+(mat[pos%tamMat]-'0'))%tamLetras];
    else if (s[i]==' ')
       s[i]=letras[mat[pos%tamMat]-'0'];
    pos++;
    i++;
  }
}

Tratando maiúsculas

Em nossa solução ainda não tratamos o caso de maiúsculas.

Isto pode ser feito da seguinte forma, usando uma variável booleana que vai guardar este valor, definindo antes TRUE e FALSE e testando no fim o valor de “maiuscula”:

Exemplo 1.15. Testa maiúsculas

#define TRUE 1
#define FALSE 0
 
int maiuscula=FALSE;
...
if (s[i]>='A' &&s[i]<='Z'){
  maiuscula=TRUE;
  s[i]=s[i]-'A' + 'a';
}
...
if (maiuscula){
    s[i]=s[i]-'a'+'A';
    maiuscula=FALSE;
}

Juntando tudo obtemos a nova versão de nossa solução (versão 9).

Tratando o retrocesso

Nossa solução funciona para todos os casos, menos o caso de retrocesso da criptografia. Vamos ver como se faz.

Aqui podemos tomar vários caminhos. O mais simples é usar uma outra string, só que invertida:

letrasInv=“zyxwvutsrqponmlkjihgfedcba “

E uma variável global que guarda se devemos avançar ou recuar. Se avanca for TRUE usamos a string letras[]; se for FALSE, usamos letrasInv; Cada vez que encontramos um espaço em branco, invertemos o sentido da criptografia. Devemos apenas tomar cuidado para achar a posição correta de s[i] em letrasInv. (versão 10)

Exemplo 1.16. criptografa() com teste de retrocesso

#define TRUE 1
#define FALSE 0
int pos=0;
int avanca=TRUE; // variável para controlar o avanço ou recuo.
 
void criptografa(char s[])
{
  char mat[]="199112340567";
  char letras[]=" abcdefghijklmnopqrstuvwxyz"; 
  char letrasInv[]="zyxwvutsrqponmlkjihgfedcba ";
  int tamLetras=27;
  int tamMat=12;
  int i=0;
  int maiuscula=FALSE;
 
 
  while (s[i]!='\0'){
    if (s[i]>='A' && s[i]<='Z'){
       maiuscula=TRUE;
       s[i]=s[i]-'A' + 'a';
    }
    if (s[i]>='a' && s[i]<='z')
       if (avanca)
          s[i]= letras[(s[i]-'a'+1+(mat[pos%tamMat]-'0'))%tamLetras];
       else
          s[i]= letrasInv[((tamLetras-1)- (s[i]-'a'+1)+(mat[pos%tamMat]-'0'))%tamLetras];
    else if (s[i]==' '){
       if (avanca)
          s[i]=letras[mat[pos%tamMat]-'0'];
       else
          s[i]=letrasInv[(mat[pos%tamMat]-'0'+(tamLetras-1))%tamLetras];
       avanca= !avanca;
       }
    if (maiuscula){
      s[i]=s[i]-'a'+'A';
      maiuscula=FALSE;
    }
    pos++;
    i++;
  }
}

Testando tudo

Agora testamos com um conjunto de dados simples e com os valores de MAXLINHA e MAXPRINT corretos.. Usaremos o arquivo de testes: (teste)

Exemplo 1.17. Arquivo de teste

#aaaa #aaaa #aaaa #aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa
#aaaa #aaaa #aaaa
#aaaa #aaaa

que fornece o seguinte resultado (saída):

Exemplo 1.18. Arquivo de saída

#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#x
awvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjb
bb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbbb#xawvt#jjbb

Melhorando nossa solução

Vamos melhorar nosso programa, retirando as variáveis globais. Usaremos variáveis estáticas, que, declaradas dentro de funções mantêm seu valor entre as chamadas. Também podemos eliminar alguns números mágicos e tornar nossa solução mais genérica. (versão 11)

Para isso, incluimos ctype.h

#include <ctype.h>

e usamos as seguintes expressões:

Exemplo 1.19. Variáveis estáticas

...
void imprime(char s[], int lim)
{ 
  int j=0;
  static int posicaoPrint=1; // deixou de ser global
 
  while( s[j]!='\0')
  {
    printf((posicaoPrint%lim)? "%c":"%c\n",s[j++]);
    posicaoPrint++;
  }
}
...
void criptografa(char s[])
{
  char mat[]="199112340567";
  char letras[]=" abcdefghijklmnopqrstuvwxyz"; 
  char letrasInv[]="zyxwvutsrqponmlkjihgfedcba ";
  int tamMat=strlen(mat);
  int tamLetras=strlen(letras);
  int i=0;
  int maiuscula=FALSE;
 
  static int pos=0;
  static int avanca=TRUE; // variável para controlar o avanço ou recuo.
...
}

Retornar para Primeira Prova - 2005-1

resolucao_primeira_prova_2005-1.txt · Última modificação: 16/01/2007 18:12:12 (edição externa)
geomatica Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0