CS50-MCZ

Uma introdução aos empreendimentos intelectuais da Ciência da Computação e da arte da programação.


Caesar


Para este problema, você implementará um programa que criptografa mensagens usando a cifra de César, conforme abaixo.

$ ./caesar 13
plaintext:  HELLO
ciphertext: URYYB
      

Começando

Abra o VS Code.

Comece clicando dentro da janela do seu terminal e execute cd sozinho. Você deve encontrar que seu "prompt" se parece com o abaixo.

$

Clique dentro da janela do terminal e execute

wget https://cdn.cs50.net/2022/fall/psets/2/caesar.zip

seguido de Enter para baixar um arquivo ZIP chamado caesar.zip em seu espaço de código. Tenha cuidado para não ignorar o espaço entre o wget e a URL a seguir, ou qualquer outro caractere!

Agora execute

unzip caesar.zip

para criar uma pasta chamada caesar. Você não precisa mais do arquivo ZIP, então pode executar

rm caesar.zip

e responda com "y" seguido por Enter no prompt para remover o arquivo ZIP que você baixou.

Agora digite

cd caesar

seguido de Enter para mover-se para o diretório (ou seja, abrir) esse diretório. Seu prompt agora deve se parecer com o abaixo.

caesar/ $

Se tudo ocorreu bem, você deve executar

ls

e ver um arquivo chamado caesar.c. Executar code caesar.c deve abrir o arquivo onde você digitará o seu código para este problema. Se não, refaça seus passos e veja se consegue determinar onde errou!

Contexto

Supostamente, César (sim, o próprio) costumava "criptografar" (ou seja, ocultar de forma reversível) mensagens confidenciais, deslocando cada letra nelas por um certo número de lugares. Por exemplo, ele poderia escrever A como B, B como C, C como D, ..., e, envolvendo alfabeticamente, Z como A. E assim, para dizer HELLO a alguém, César poderia escrever IFMMP em seu lugar. Ao receber tais mensagens de César, os destinatários teriam que "descriptografá-las" deslocando as letras na direção oposta pelo mesmo número de lugares.

A segurança desse "criptossistema" dependia apenas de César e dos destinatários saberem um segredo, o número de lugares pelos quais César havia deslocado suas letras (por exemplo, 1). Não é particularmente seguro pelos padrões modernos, mas, ei, se você talvez seja o primeiro do mundo a fazer isso, é bastante seguro!

O texto não criptografado é geralmente chamado de texto simples. O texto criptografado é geralmente chamado de texto cifrado. E o segredo usado é chamado de chave.

Para ser claro, aqui está como criptografar HELLO com uma chave de 1 produz IFMMP:

plaintext H E L L O
+ key 1 1 1 1 1
= ciphertext I F M M P

Mais formalmente, o algoritmo de Cifra de César cifra mensagens "rotacionando" cada letra por k posições. Mais precisamente, se p é algum texto simples (ou seja, uma mensagem não criptografada), pi é o ith caractere em p, e k é uma chave secreta (ou seja, um número inteiro não negativo), então cada letra, ci, no texto cifrado, c, é calculado como:

ci = (pi + k) % 26

onde % 26 aqui significa "resto da divisão por 26". Esta fórmula talvez faça a cifra parecer mais complicada do que realmente é, mas na verdade é apenas uma forma concisa de expressar o algoritmo com precisão. De fato, para fins de discussão, pense em A (ou a) como 0, B (ou b) como 1, ..., H (ou h) como 7, I (ou i) como 8, ..., e Z (ou z) como 25. Suponha que César queira dizer confidencialmente "Hi" para alguém usando, desta vez, uma chave k de 3. E então seu texto simples, p, é Hi, caso em que o primeiro caractere do seu texto simples, p0, é H (também conhecido como 7), e o segundo caractere do seu texto simples, p1, é i (também conhecido como 8). O primeiro caractere do seu texto cifrado, c0, é assim K, e o segundo caractere do seu texto cifrado, c1, é assim L. Faz sentido?

Vamos escrever um programa chamado caesar que permite cifrar mensagens usando a cifra de César. No momento em que o usuário executa o programa, eles devem decidir, fornecendo um argumento de linha de comando, qual será a chave na mensagem secreta que eles fornecerão em tempo de execução. Não devemos necessariamente assumir que a chave do usuário será um número; embora possamos assumir que, se for um número, será um inteiro positivo.

Aqui estão alguns exemplos de como o programa pode funcionar. Por exemplo, se o usuário inserir uma chave de 1 e um texto simples de HELLO:

$ ./caesar 1
plaintext:  HELLO
ciphertext: IFMMP  

Aqui está como o programa poderia funcionar se o usuário fornecer uma chave de 13 e um texto simples de hello, world:

$ ./caesar 13
plaintext:  hello, world
ciphertext: uryyb, jbeyq    

Observe que nem a vírgula nem o espaço foram "deslocados" pela cifra. Apenas os caracteres alfabéticos são rotacionados!

E que tal mais um exemplo? Aqui está como o programa poderia funcionar se o usuário fornecer uma chave de 13 novamente, com um texto simples mais complexo:

$ ./caesar 13
plaintext:  be sure to drink your Ovaltine
ciphertext: or fher gb qevax lbhe Binygvar  
Por quê?

Observe que o caso da mensagem original foi preservado. As letras minúsculas permanecem minúsculas e as letras maiúsculas permanecem maiúsculas.

E o que acontece se o usuário não cooperar, fornecendo um argumento de linha de comando que não é um número? O programa deve lembrar o usuário de como usar o programa:

$ ./caesar HELLO
Usage: ./caesar key  

Ou se o usuário não fornecer nenhum argumento de linha de comando? O programa deve lembrar o usuário como usar o programa:

$ ./caesar
Usage: ./caesar key  

Ou realmente, realmente não coopera, fornecendo mais de um argumento de linha de comando? O programa deve lembrar o usuário de como usar o programa:

$ ./caesar 1 2 3
Usage: ./caesar key  
Assista a uma gravação

Especificação

Projete e implemente um programa, caesar, que criptografa mensagens usando o cifra de César.

Aconselhamento

Como começar? Vamos abordar este problema passo a passo.

Pseudocódigo

Primeiro, tente escrever uma função main em caesar.c que implemente o programa usando apenas pseudocódigo, mesmo que ainda não saiba como escrevê-lo em código real.

Dica

Há mais de uma maneira de fazer isso, então aqui está apenas uma!

int main(void)
{
    // Make sure program was run with just one command-line argument

    // Make sure every character in argv[1] is a digit

    // Convert argv[1] from a `string` to an `int`

    // Prompt user for plaintext

    // For each character in the plaintext:

        // Rotate the character if it's a letter
}    

Tudo bem editar o seu próprio pseudocódigo depois de ver o nosso aqui, mas não simplesmente copie e cole o nosso no seu!


Contando argumentos da linha de comando

Independentemente do pseudocódigo, vamos primeiro escrever apenas o código C que verifica se o programa foi executado com um único argumento da linha de comando antes de adicionar funcionalidades adicionais.

Especificamente, modifique a função main em caesar.c de tal forma que, se o usuário não fornecer nenhum argumento da linha de comando, ou dois ou mais, a função imprime "Usage: ./caesar key\n" e, em seguida, retorna 1, efetivamente encerrando o programa. Se o usuário fornecer exatamente um argumento da linha de comando, o programa não deve imprimir nada e simplesmente retornar 0. O programa deve, portanto, comportar-se conforme abaixo.

$ ./caesar
Usage: ./caesar key  
$ ./caesar 1 2 3
Usage: ./caesar key  
$ ./caesar 1  
Dicas
  • Lembre-se que você pode imprimir com o printf.
  • Lembre-se que uma função pode retornar um valor com return.
  • Lembre-se que argc contém o número de argumentos de linha de comando passados para um programa, além do nome do próprio programa.

Verificando a Chave

Agora que seu programa está (esperamos!) aceitando a entrada conforme prescrito, é hora de mais um passo.

Adicione ao arquivo caesar.c, abaixo do main, uma função chamada, por exemplo, only_digits, que recebe uma string como argumento e retorna true se essa string contiver apenas dígitos, de 0 a 9, caso contrário, retorna false. Certifique-se de adicionar o protótipo da função acima do main também.

Dicas
  • Provavelmente você desejará um protótipo como:
    bool only_digits(string s);  

    E certifique-se de incluir cs50.h no topo do seu arquivo, para que o compilador reconheça string (e bool).

  • Lembre-se de que uma string é apenas uma matriz de chars.
  • Lembre-se de que strlen, declarado em string.h, calcula o comprimento de uma string.
  • Você pode achar isdigit, declarado em ctype.h, útil, conforme o manual.cs50.io. Mas observe que ele verifica apenas um char por vez!

Então modifique main de forma que ele chame only_digits em argv[1]. Se essa função retornar false, então main deve imprimir "Usage: ./caesar key\n" e retornar 1. Caso contrário, main deve simplesmente retornar 0. O programa deve, portanto, se comportar conforme abaixo:

$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key

Usando a Chave

Agora modifique a função main de tal forma que ela converta argv[1] para um int. Você pode achar útil a função atoi, declarada em stdlib.h, conforme o manual.cs50.io. E então use get_string para solicitar ao usuário algum texto simples com "plaintext: ".

Em seguida, implemente uma função chamada, por exemplo, rotate, que recebe um char como entrada e também um int, e rotaciona aquele char por tantas posições quanto o número de vezes especificado, caso seja uma letra (ou seja, alfabética), enrolando de Z para A (e de z para a) conforme necessário. Se o char não for uma letra, a função deve retornar o mesmo char inalterado.

Dicas
  • É provável que você queira um protótipo como:
    char rotate(char c, int n);    

    Uma chamada de função como

    rotate('A', 1)    

    ou até mesmo

    rotate('A', 27)    

    deverá retornar 'B'. E uma chamada de função como

    rotate('!', 13)    

    deve retornar '!'.

  • Lembre-se de que você pode "converter" explicitamente um caractere char em um int com (char), e um int em um char com (int). Ou você pode fazer isso implicitamente tratando um como o outro.
  • Provavelmente você desejará subtrair o valor ASCII de 'A' de quaisquer letras maiúsculas, para tratar 'A' como 0, 'B' como 1 e assim por diante, enquanto realiza operações aritméticas. E então adicioná-lo de volta quando terminar com o mesmo.
  • Provavelmente você desejará subtrair o valor ASCII de 'a' de quaisquer letras minúsculas, para tratar 'a' como 0, 'b' como 1 e assim por diante, enquanto realiza operações aritméticas. E então adicioná-lo de volta quando terminar com o mesmo.
  • Você pode achar algumas outras funções declaradas em ctype.h úteis, conforme manual.cs50.io.
  • Provavelmente você encontrará o operador % útil quando precisar "dar a volta" aritmeticamente de um valor como 25 para 0.

Em seguida, modifique a função main de forma que ela imprima "ciphertext: " e, em seguida, percorra cada char do texto simples do usuário, chamando a função rotate em cada caractere e imprimindo o valor de retorno.

Dicas
  • Lembre-se de que o printf pode imprimir um caractere (char) usando %c.
  • Se você não estiver vendo nenhuma saída quando chamar o printf, provavelmente é porque está imprimindo caracteres fora da faixa ASCII válida de 0 a 127. Tente imprimir caracteres temporariamente como números (usando %i em vez de %c) para ver quais valores está imprimindo!

Como Testar o Seu Código

Execute o seguinte para avaliar a correção do seu código usando check50. Mas certifique-se de compilar e testar também por conta própria!

check50 cs50/problems/2023/x/caesar

Execute o código abaixo para avaliar o estilo do seu código usando style50.

style50 caesar.c

Como Enviar

No seu terminal, execute abaixo para enviar seu trabalho.

submit50 cs50/problems/2023/x/caesar