César
Problema a Resolver
Supuestamente, César (sí, ese César) solía "cifrar" (es decir, ocultar de forma reversible) mensajes confidenciales desplazando cada letra dentro del mensaje un cierto número de posiciones. Por ejemplo, podría escribir la A como B, la B como C, la C como D, ..., y regresando alfabéticamente, la Z como A. Por tanto, para decirle HOLAb a alguien, César podría escribir en su lugar IFMMP. Al recibir este tipo de mensajes de César, los destinatarios deberían "descifrarlos" desplazando las letras en la dirección opuesta el mismo número de posiciones.
La confidencialidad de este "criptosistema" dependía de que sólo César y los destinatarios conocieran un secreto, el número de posiciones que César había desplazado sus letras (por ejemplo, 1). No es particularmente seguro para los estándares modernos, pero, ¡oye!, si eres quizás el primero en el mundo en hacerlo, ¡bastante seguro!
El texto sin cifrar se llama generalmente texto plano. El texto cifrado se llama generalmente texto cifrado. Y el secreto utilizado se llama clave.
Para ser claros, a continuación, te mostramos cómo al cifrar HOLAb
con una clave de \(1\) produce IFMMP
:
texto plano | H |
O |
L |
A |
b |
---|---|---|---|---|---|
+ clave | \(1\) | \(1\) | \(1\) | \(1\) | \(1\) |
= texto cifrado | I |
F |
M |
M |
P |
Más formalmente, el algoritmo (es decir, cifrado) de César cifra mensajes "rotando" cada letra \(k\) posiciones. Más formalmente, si \(p\) es un texto plano (es decir, un mensaje sin cifrar), \(p_i\) es el \(i^{ésimo}\) carácter en \(p\), y \(k\) es una clave secreta (es decir, un entero no negativo), entonces cada letra, \(c_i\), en el texto cifrado, \(c\), se calcula como
\[c_i = (p_i + k)\space\%\space26\]
donde \(\%\space26\) aquí significa "resto al dividir entre 26." Esta fórmula quizás hace que el cifrado parezca más complicado de lo que es, pero es realmente sólo una forma concisa de expresar el algoritmo con precisión. De hecho, para fines de discusión, piensa en la A (o a) como \(0\), la B (o b) como \(1\), ... la H (o h) como \(7\), la I (o i) como \(8\), ... y la Z (o z) como \(25\). Supongamos que César sólo quiere decirle Hola
a alguien confidencialmente usando, esta vez, una clave, \(k\), de 3. Por tanto, su texto plano, \(p\), es Hola
, en cuyo caso el primer carácter de su texto plano, \(p_0\), es H
(también conocida como 7), y el segundo carácter de su texto plano, \(p_1\), es o
(también conocida como 14). El primer carácter de su texto cifrado, \(c_0\), es por tanto K
, y el segundo carácter de su texto cifrado, \(c_1\), es por tanto r
. ¿Tiene sentido?
En un archivo llamado caesar.c
en una carpeta llamada caesar
, escribe un programa que te permita cifrar mensajes utilizando el cifrado de César. En el momento en que el usuario ejecute el programa, debe decidir, proporcionando un argumento de línea de comandos, cuál debe ser la clave en el mensaje secreto que proporcionará en tiempo de ejecución. No debemos asumir necesariamente que la clave del usuario será un número; aunque puedes asumir que, si es un número, será un entero positivo.
Demostración
Especificación
Diseña e implementa un programa, caesar
, que cifra mensajes utilizando el cifrado de César.
- Implementa tu programa en un archivo llamado
caesar.c
en un directorio llamadocaesar
. - Tu programa debe aceptar un solo argumento de línea de comandos, un entero no negativo. Llamémoslo \(k\) para fines de discusión.
- Si tu programa se ejecuta sin ningún argumento de línea de comandos o con más de un argumento de línea de comandos, tu programa debe imprimir un mensaje de error de tu elección (con
printf
) y devolver desdemain
un valor de1
(que tiende a significar un error) inmediatamente. - Si alguno de los caracteres del argumento de la línea de comandos no es un dígito decimal, tu programa debe imprimir el mensaje
Uso: ./caesar clave
y devolver desdemain
un valor de1
. - No asumas que \(k\) será menor o igual que 26. Tu programa debe funcionar para todos los valores enteros integrales no negativos de \(k\) menores que \(2^{31} - 26\). En otras palabras, no necesitas preocuparte si tu programa eventualmente se rompe si el usuario elige un valor para \(k\) que sea demasiado grande o casi demasiado grande para caber en un
int
. (Recuerda que unint
puede desbordarse.) Pero, incluso si \(k\) es mayor que \(26\), los caracteres alfabéticos en la entrada de tu programa deben seguir siendo caracteres alfabéticos en la salida de tu programa. Por ejemplo, si \(k\) es \(27\),A
no debe convertirse en\
incluso si\
está a \(27\) posiciones deA
en ASCII, según asciitable.com;A
debe convertirse enB
, ya queB
está a \(27\) posiciones deA
, siempre que regreses de laZ
a laA
. - Tu programa debe generar
plaintext:
(con dos espacios pero sin una nueva línea) y luego solicitar al usuario unacadena
de texto plano (usandoget_string
). - Tu programa debe generar
ciphertext:
(con un espacio pero sin una nueva línea) seguido del texto cifrado correspondiente del texto plano, con cada carácter alfabético en el texto plano "rotado" \(k\) posiciones; Los caracteres no alfabéticos deben emitirse sin cambios. - Tu programa debe conservar las mayúsculas y minúsculas: las letras mayúsculas, aunque rotadas, deben seguir siendo mayúsculas; Las letras minúsculas, aunque rotadas, deben seguir siendo minúsculas.
- Después de generar texto cifrado, debes imprimir una nueva línea. Tu programa debe salir devolviendo
0
desdemain
.
Consejo
¿Cómo empezar? Abordemos este problema paso a paso.
Pseudocódigo
Primero escribe, intenta escribir una función main
en caesar.c
que implemente el programa usando sólo pseudocódigo, incluso si no estás (¡aún!) seguro de cómo escribirlo en el código real.
¡Hay más de una forma de hacer esto, así que aquí tienes sólo una!
int main(int argc, string argv[])
{
// Asegúrate de que el programa se ejecutó con un solo argumento de línea de comandos
// Asegúrate de que cada carácter en argv[1] sea un dígito
// Convierte argv[1] de una `cadena` a un `int`
// Pídele al usuario el texto plano
// Para cada carácter en el texto plano:
// Rota el carácter si es una letra
}
¡Está bien editar tu propio pseudocódigo después de ver el nuestro aquí, pero no simplemente copies/pegues el nuestro en el tuyo!
Conteo de argumentos de línea de comandos
Independientemente de tu pseudocódigo, primero escribamos solo el código C que verifica si el programa se ejecutó con un único argumento de línea de comandos antes de agregar funcionalidad adicional.
Específicamente, modifica main
en caesar.c
de tal manera que, si el usuario no proporciona ningún argumento de línea de comandos, o dos o más, la función imprima "Uso: ./caesar clave\n"
y luego devuelva 1
, saliendo efectivamente del programa. Si el usuario proporciona exactamente un argumento de línea de comandos, el programa no debe imprimir nada y simplemente devolver 0
. Por lo tanto, el programa debe comportarse según lo siguiente.
$ ./caesar
Uso: ./caesar clave
$ ./caesar 1 2 3
Uso: ./caesar clave
$ ./caesar 1
Sugerencias
- Recuerda que puedes imprimir con
printf
. - Recuerda que una función puede devolver un valor con
return
. - Recuerda que
argc
contiene el número de argumentos de línea de comandos pasados a un programa, más el propio nombre del programa.
Verificación de la clave
Ahora que tu programa (¡con suerte!) está aceptando la entrada como se prescribe, es hora de dar otro paso.
Agrega a caesar.c
, debajo de main
, una función llamada, por ejemplo, only_digits
que tome una cadena
como argumento y devuelva true
si esa cadena
contiene solo dígitos, del 0
al 9
, de lo contrario devuelve false
. Asegúrate de agregar también el prototipo de la función sobre main
.
Sugerencias
-
Lo más probable es que quieras un prototipo como: bool only_digits(string s);
Y asegúrate de incluir
cs50.h
en la parte superior de tu archivo, para que el compilador reconozcastring
(ybool
). -
Recuerda que una
cadena
es solo una matriz dechar
. - Recuerda que
strlen
, declarado enstring.h
, calcula la longitud de unacadena
. - Es posible que encuentres útil
isdigit
, declarado enctype.h
, según manual.cs50.io. ¡Pero ten en cuenta que solo verifica unchar
a la vez!
Luego modifica main
de tal manera que llame a only_digits
en argv[1]
. Si esa función devuelve false
, entonces main
debe imprimir "Uso: ./caesar clave\n"
y devolver 1
. De lo contrario, main
simplemente debe devolver 0
. Por lo tanto, el programa debe comportarse según lo siguiente:
$ ./caesar 42
$ ./caesar plátano
Uso: ./caesar clave
Uso de la clave
Ahora modifica main
de tal manera que convierta argv[1]
a un int
. Es posible que encuentres útil atoi
, declarado en stdlib.h
, según manual.cs50.io. Y luego usa get_string
para solicitar al usuario un texto sin formato con "texto sin formato: "
.
Luego, implementa una función llamada, por ejemplo, rotate
, que tome un char
como entrada y también un int
, y gire ese char
en esa cantidad de posiciones si es una letra (es decir, alfabético), envolviendo desde Z
a A
(y desde z
a a
) según sea necesario. Si el char
no es una letra, la función debería devolver el mismo char
sin cambios.
Sugerencias
-
Lo más probable es que quieras un prototipo como: char rotate(char c, int n);
Una llamada a función como rotate('A', 1)
o incluso rotate('A', 27)
debe devolver
'B'
. Y una llamada a función como rotate('!', 13)debe devolver
'!'
. -
Recuerda que puedes "convertir" explícitamente un
char
a unint
con(int)
y unint
a unchar
con(char)
. O puedes hacerlo implícitamente simplemente tratando uno como el otro. - Lo más probable es que quieras restar el valor ASCII de
'A'
de todas las letras mayúsculas, para tratar'A'
como0
,'B'
como1
, etc., mientras realizas operaciones aritméticas. Y luego agrégalo de nuevo cuando termines con el mismo. - Lo más probable es que quieras restar el valor ASCII de
'a'
de todas las letras minúsculas, para tratar'a'
como0
,'b'
como1
, etc., mientras realizas operaciones aritméticas. Y luego agrégalo de nuevo cuando termines con el mismo. - Es posible que encuentres útiles algunas otras funciones declaradas en
ctype.h
, según manual.cs50.io. - Lo más probable es que encuentres
%
útil cuando "envuelvas" aritméticamente de un valor como25
a0
.
Luego modifica main
de tal manera que imprima "texto cifrado: "
y luego itere sobre cada char
en el texto sin formato del usuario, llamando a rotate
en cada uno e imprimiendo el valor de retorno del mismo.
Sugerencias
- Recuerda que
printf
puede imprimir unchar
usando%c
. - Si no ves ninguna salida cuando llamas a
printf
, lo más probable es que se deba a que estás imprimiendo caracteres fuera del rango ASCII válido de 0 a 127. ¡Intenta imprimir caracteres temporalmente como números (usando%i
en lugar de%c
) para ver qué valores estás imprimiendo!
Tutorial
Cómo probar
Corrección
En tu terminal, ejecuta lo siguiente para verificar la corrección de tu trabajo.
check50 cs50/problems/2024/x/caesar
Cómo usar debug50
¿Buscas ejecutar debug50
? Puedes hacerlo de la siguiente manera, después de compilar tu código correctamente con make
,
debug50 ./caesar KEY
donde KEY
es la clave que proporcionas como argumento de línea de comandos para tu programa. Tenga en cuenta que ejecutar
debug50 ./caesar
(idealmente) hará que tu programa se cierre al solicitarle al usuario una clave.
Estilo
Ejecuta lo siguiente para evaluar el estilo de tu código usando style50
.
style50 caesar.c
Cómo enviar
En tu terminal, ejecuta lo siguiente para enviar tu trabajo.
submit50 cs50/problems/2024/x/caesar