Segundo semestre de 2008
Terceiro Exercício-Programa
(em revisão ainda...)
Data de entrega: 29 de junho de 2008
Um tipo de ciframento, chamado de substituição monoalfabética, consiste em simplesmente trocar uma letra do alfabeto por outra, de acordo com uma permutação das letras do alfabeto. Letras maiúsculas e minúsculas não são diferenciadas. Nos textos cifrados, usualmente não há acentos, nem espaçamento ou pontuação.
A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
I | S | N | L | B | Q | C | R | D | T | P | E | Z | A | G | U | Y | K | O | X | J | W | H | M | V | F |
Exemplo:
Texto: Que exercício-programa legal!
Texto cifrado: YJBBMBKNDNDGUKGCKIZIEBCIE
De fato, substituindo-se as letras da frase "Que exercício-programa legal!" de acordo com a permutação dada acima, obtemos "YJB BMBKNDNDG-UKGCKIZI EBCIE!". Removendo-se os espaços em branco e a pontuação, chegamos ao texto cifrado acima.
Agora você. Supondo que foi usada a permutação das letras dada acima, a que texto corresponde a seguinte mensagem cifrada?
Texto cifrado: WGJCGOXIKLBQIFBKBOXBBMBKNDNDGUKGCKIZI
Texto: ??????
Note que existem 26! (aproximadamente 4x1026) permutações diferentes das letras do alfabeto. Assim, se a permutação usada não é conhecida, existem aproximadamente 4x1026 ciframentos possíveis.
Nesse exercício-programa, resumidamente, você terá que escrever um programa em C que tenha como entrada um texto cifrado e ajude o usuário no processo de deciframento, assumindo que a cifra utilizada foi a substituição monoalfabética.
A técnica de deciframento que você deve utilizar é baseada no fato de que as letras do alfabeto não ocorrem todas com a mesma freqüência. Nos textos em português, a letra 'A' é a mais comum, 'E' é a segunda mais comum, e assim por diante, até 'K', 'W' e 'Y' que quase não ocorrem. Então, como um primeiro passo para decifrar o texto, você deve contar a ocorrência de cada letra no texto cifrado e tentar relacionar essas freqüências com a freqüência padrão conhecida das letras em português. Por exemplo, se 'Q' foi a letra que mais ocorreu no texto cifrado, é razoável desconfiar que 'Q' está substituindo no texto cifrado a letra 'A' do texto original. Logo, você trocará todas as ocorrências da letra 'Q' no texto cifrado pela letra 'A'. A mesma coisa para cada uma das 26 letras do alfabeto. Feito isso, o programa passará a uma fase de refinamento, em que auxiliará o usuário a determinar qual é a substituição correta que leva ao deciframento total do texto, fazendo as trocas de letras sugeridas pelo usuário.
char *portugues = "AEOSIRNDTCMUPLVGBFQHJXZKWY";Tal declaração corresponde à declaração de um vetor de caracteres de nome portugues, com 26 posições, inicializado com as letras exibidas acima, ou seja, 'A' na posição 0, 'E' na posição 1, 'O' na posição 2 e assim por diante. Para facilitar, declare tal variável como uma variável global, fora da função main, antes de todas as funções que você tiver no seu programa.
Exemplo: Se o arquivo de entrada contiver o texto
O principal benefício de aprender e fazer matemática é que ela desenvolve a habilidade de raciocinar sobre estruturas formalmente definidas como aquelas em computação e suas aplicações.que não está cifrado, mas é uma tradução da frase inicial do artigo "Why universities require computer science students to take math?" [Communications of the ACM, Volume 46, Number 9 (2003), Pages 36-39], o seu programa deve obter a seguinte ordem:
E (que aparece 25 vezes) A (aparece 22 vezes) I (aparece 12 vezes) O (aparece 11 vezes) C (aparece 10 vezes) R (aparece 10 vezes) S (aparece 9 vezes) D (aparece 8 vezes) L (aparece 7 vezes) M (aparece 7 vezes) N (aparece 7 vezes) T (aparece 6 vezes) U (aparece 6 vezes) P (aparece 5 vezes) F (aparece 4 vezes) B (aparece 3 vezes) Q (aparece 2 vezes) V (aparece 2 vezes) H (aparece 1 vezes) Z (aparece 1 vezes) e as demais letras, que não aparecem nenhuma vez, numa ordem arbitrária.
Você pode supor que no texto não há acentos.
Digamos que o resultado de tal ordenação fique guardado no vetor permutacao, com 26 posições.
A primeira fase do deciframento consiste em trocar no texto cifrado a ocorrência da letra mais freqüente pela letra mais freqüente em português, a segunda letra mais freqüente pela segunda letra mais freqüente em português, e assim por diante.
Feito isso, o seu programa mostra o texto resultante das trocas sugeridas, substituindo (apenas na impressão) a letra permutacao[i] pela letra portugues[i], para cada i entre 0 e 25. O texto assim impresso é o resultado da primeira fase - a primeira tentativa de deciframento do texto.
Em geral o texto obtido depois desta primeira tentativa de deciframento não é a mensagem correta decifrada. Algumas letras não terão sido trocadas pelas letras corretas. O programa entra então em uma segunda fase, onde se inicia uma interação com o usuário.
Repetidamente, o programa imprime para o usuário a seguinte informação:
Para que fique mais claro o que o seu programa deve fazer, aqui você encontra um programa executável que faz exatamente o que o seu programa deve fazer. Brinque com este programa, usando os vários textos cifrados abaixo, antes de começar a pensar em como escrever o seu programa.
Caso você tenha problemas para executar o programa decifra, siga as seguintes instruções. Entre na sua conta da rede linux e execute o seguinte comando num diretório a sua escolha:
cp ~cris/mac110/* .
Feito isso, nesse seu diretório você deve ter o arquivo decifra e os 6 arquivos cifrados acima. Execute o decifra, dando como entrada qualquer um destes arquivos e teste o programa!
Em algumas situações, quando o programa faz leitura de caracteres
do teclado, alguns problemas são comuns, como você já observou no
EP2. O mais comum e mais chato é o programa ignorar um comando de
leitura simplesmente. Isso tem a ver com o programa fazer a leitura de
um buffer intermediário, e só apenas quando o buffer está vazio, ele
faz a leitura do teclado mesmo. Para que o programa não leia
caracteres indesejáveis que estejam no buffer, basta utilizar como
formato de leitura Sintam-se a vontade para implementar outras funções
que vocês achem conveniente.
No miolo do seu programa, toda vez que você quiser ler alguma coisa do
arquivo de entrada, utilize a função fscanf. Esta função
funciona de forma semelhante ao scanf, exceto que ela possue
um parâmetro a mais: o nome do arquivo de onde está sendo feita a
leitura. Numa primeira fase, escreva apenas um programa que leia um
texto de um arquivo de entrada e o imprima na tela.
No EP, você vai precisar detectar quando o arquivo terminou para
concluir a leitura dos dados. Para tanto, utilize a função
feof, que tem como parâmetro um identificador de arquivo, e
devolve 1 se o arquivo terminou, 0 em caso contrário. (Veja a
adição no exemplo abaixo.)
Exemplos:
O lcc, quando você dá como nome do arquivo simplesmente
entrada.txt, procura o arquivo no chamado diretório
padrão. Assim, certifique-se de que o seu arquivo de entrada
encontra-se neste diretório, ou ele não será encontrado. Uma outra
alternativa é dar, quando o seu programa pedir para você digitar o
nome do arquivo, o nome completo do arquivo, isto é, o nome
incluindo os diretórios todos onde ele se encontra. Cuidado no entanto
com as "\" no nome do arquivo.
" %c", com um caractere branco antes
do %c.
Funções obrigatórias
O seu programa deve conter pelo menos as duas funções abaixo:
que lê de um arquivo cujo nome é dado pelo usuário um arquivo
texto, e o armazena no vetor texto, devolvendo em
*n o número de caracteres do texto. Além disso, a
função também imprime o texto lido.
que recebe um caractere como parâmetro e devolve 1 se o
caractere for uma letra, 0 caso contrário.
que tem como parâmetro de entrada um vetor texto onde as
primeiras n posições armazenam um texto, e como
parâmetro de saída um vetor cont, com 26 posições,
onde a função deve armazenar o número de vezes que cada letra
aparece no texto dado no vetor texto. Para fazer
isso, use a função anterior.
que recebe como parâmetro de entrada um vetor cont,
de 26 posições, com o número de aparições de cada letra do
alfabeto, e devolve no parâmetro de saída perm uma
permutação das letras do alfabeto em ordem não-crescente de
número de aparições.
devolve o índice onde letra aparece no vetor
v, que armazena uma permutação das letras do
alfabeto.
imprime na tela os n caracteres armazenados no vetor
texto (ou parte deles, se o usuário não quiser ver
todo o texto), convertidos de acordo com a ordem das letras
em perm e a ordem das letras por freqüência em
português. Para fazer isso, use a função anterior.
devolve o próprio caractere ch, se ele não for uma letra
minúscula, ou a maiúscula correspondente, se ele for uma letra
minúscula.
devolve a permutação perm do alfabeto ajustada, de
forma que as letras l1 e l2 sejam trocadas
no momento da decodificação do texto. Preste atenção aqui! O
que você deve fazer para isso é trocar no vetor perm
as letras correspondentes a l1 e l2 no vetor
portugues. Use a função posição para isso.
imprime em um arquivo cujo nome é escolhido pelo usuário o
texto decifrado. Essa função é semelhante a função
decodificaemostra, mas imprime o texto todo, sem
interrupções. Utilize para isso novamente a função
posicao.
Arquivos texto
Para fazer a leitura de um arquivo de entrada, utilize a seguinte
receita no seu programa, dentro da função main():
/* Declaração das variáveis para leitura e gravação em arquivos */
char nome[40]; /* para o nome do arquivo de entrada */
FILE *entrada;
/* Primeiros comandos do seu programa */
/* Abertura do arquivo de entrada */
printf("Digite o nome do arquivo de entrada: ");
scanf("%s", nome);
if ((entrada = fopen(nome, "r")) == NULL) {
printf("Arquivo de entrada nao encontrado!\n");
exit(0);
}
/* Último comando do seu programa (exceto pelo return) */
fclose(entrada);
/* Leitura de três números inteiros do arquivo de entrada */
fscanf(entrada, "%d %d %d ", &n, &p, &m);
/* Leitura de um caractere do arquivo de entrada */
fscanf(entrada, "%c", &ch);
/* Leitura de um número real do arquivo de entrada */
fscanf(entrada, "%f ", &x);
/* enquanto o arquivo entrada ainda tem dados a serem lidos... */
while (!feof(entrada)) {
...
}
Para criar um arquivo de entrada, você pode utilizar o próprio lcc.
Para tanto, apenas crie um arquivo novo, dando-lhe o nome, por
exemplo, de entrada.txt, e digite nele, por exemplo, o texto
acima (sem os acentos, se for o caso). Grave o arquivo. Não compile
este arquivo, claro, já que ele não é um programa em C.
Observações
/********************************************************/
/* Fulano de Tal */
/* Exercicio-Programa xx */
/* Curso yy - Turma zz -- Professor: Ciclano de Tal */
/********************************************************/