JavaRush /Курсы /Harvard CS50 /Задание 1. Написать шифр Цезаря

Задание 1. Написать шифр Цезаря

Harvard CS50
2 уровень , 8 лекция
Открыта

Условие

Написать в файле caesar.c, программу, шифрующую текст с помощью шифра Цезаря. На вход программы нужно подавать один аргумент командной строки: не негативное целое число. Для простоты назовем его k. Если пользователь выполняет программу без аргументов командной строки или более, чем с одним аргументом, приложение должно возмутиться и вернуть значение 1 (обычно так обозначают ошибки):

return 1;

Во всех остальных случаях программа запрашивает у пользователя текст, который нужно зашифровать, затем выводит на экран текст, зашифрованный ключом k (т.е., смещенный на k позиций вправо по циклу). Если в тексте есть символы, выходящие за пределы английского алфавита, их программа не меняет. После вывода шифрованного текста, приложение завершает работу, main возвращает 0

return 0;

Если main не возвращает нуль явно, он возвращается автоматически (на самом деле int — тип, возвращаемый main, но об этом в другой раз). Согласно конвенции (правилам хорошего тона в программировании), если вы явно возвращаете 1 чтобы указать на ошибку, то нужно вернуть и 0 в качестве указателя на успешное завершение работы программы. 

Хотя в английском алфавите только 26 букв, k может быть и больше 26. По сути, ключ k = 27 даст тот же результат, что и k = 1, но нужно позволить пользователю вводить любое неотрицательное число, не превышающее 2^31 – 26 (ограничение связано с тем, что число должно поместиться в int). Программа также должна учитывать, что строчные буквы шифруются строчными, а прописные — прописными. 

С чего начинаем?

Поскольку приложение должно принять значение k непосредственно в строке аргументов, заголовок функции main у нас имеет следующий вид: 

int main(int argc, string argv[])

Из шестой лекции вы знаете, что argv — это массив строк. Массив можно представить, как ряд шкафчиков-ячеек в спортзале. В каждом из них спрятано некоторое значение. В нашем случае, внутри каждой ячейки лежит аргумент типа string.

Чтобы открыть первый шкафчик, используем argv[0], второй — argv[1] и так далее. Если у нас есть n замков, то нам нужно остановиться на argv[n — 1], поскольку argv[n] уже не существует (или существует, но принадлежит кому-то ещё, нам лучше его не трогать). 

Таким образом, вы можете получить доступ к аргументу k следующим образом:

string k = argv[1];

Мы полагаем, что там действительно что-то есть! Напомним, argc — переменная типа int, равная количеству строк argv. Значит, лучше проверить значение argc прежде, чем пытаться открыть ячейку, ведь может статься, что её не существует. 

В идеале argc = 2. Почему так? Внутри argv[0] обычно находится имя программы. То есть, argc всегда не меньше 1. Но нашей программе нужно, чтобы пользователь предоставил аргумент командной строки k, следовательно, argc = 2. Естественно, если пользователь в командной строке введет более одного аргумента, argc также подрастает и может быть больше, чем 2.

Если пользователь вводит целое число в строку, это еще не значит, что внесенное значение будет автоматически сохранено в тип int. Точнее, оно НЕ будет. Оно будет string, даже если выглядит точь-в-точь, как int! Так что нам нужно конвертировать string в int самостоятельно. К счастью, существует функция atoi, созданная для этих целей. Её синтаксис:

int k = atoi(argv[1]);

Обратите внимание: k имеет тип int, поэтому с ним можно провернуть арифметические действия. С этой функцией не нужно беспокоиться, введёт ли пользователь целое число, или, скажем, foo: в таком случае atoi возвратит 0.

Функция atoi объявлена в библиотеке stdlib.h, поэтому не забудьте прописать её директивой #include в начале программы. Код и без этого скомпиллируется, поскольку мы уже включили эту функцию в библиотеку cs50.h. Тем не менее, лучше доверять нативным библиотекам. 

Итак, вы получили k, сохраненное как int. Теперь запросим ввод текста. Если вы делали задания первой недели, то уже знакомы с функцией библиотеки CS50, которая называется GetString. Она-то нам и поможет. 

После того, как вы получили k и начальный текст, приступим к шифрованию. Напомним, вы можете пройтись по всем символам строки и напечатать их с помощью следующего цикла:

for (int i = 0, n = strlen(p); i < n; i++)
{
    printf("%c", p[i]);
}

Другими словами, точно так же, как argv — массив строк, string является массивом символов. Поэтому мы можем использовать квадратные скобки для доступа к отдельным элементам строки точно так же, как получать отдельные строки в argv. Конечно, нет ничего криптографического в печати каждого из символов. Или, технически, когда k = 0. Но мы же должны помочь Цезарю зашифровать его текст! Аве, Цезарь!

Чтобы использовать strlen, нужно подключить ещё одну библиотеку. 

Поскольку мы автоматизируем некоторые проверочные тесты, программа должна себя вести себя ровно следующим образом: 

username:~/workspace/pset2 $ ./caesar 13
Be sure to drink your Ovaltine!
Or fher gb qevax lbhe Binygvar!

Помимо atoi, вы можете найти другие классные функции в библиотеках ctype.h и stdlib.h. Для этого перейдите по ссылке и поройтесь там немного. Например, isdigit — явно что-то интересное=). 

Когда переходите от Z к A (или от z к a), не забывайте об операторе деления по модулю % в языке Си. Также изучите таблицу, она показывает символы ASCII не только для букв. 

Чтобы проверить правильность работы программы с check50, выполните следующее:

check50 2015.fall.pset2.caesar caesar.c

А если вам интересно поиграть с кодом, сделанным сотрудниками СS50, выполните команду:

~cs50/pset2/caesar

Кстати, uggc://jjj.lbhghor.pbz/jngpu?i=bUt5FWLEUN0.

Разбор задания

Вот что вам нужно сделать:

  • Получить ключ
  • Получить текст
  • Зашифровать
  • Вывести на экран зашифрованное сообщение

Для этого:

  1. Формируем функцию main так, чтобы пользователь вводил ключ в командной строке и проверяем ключ на корректность.
    int main(int argc, string argv[])

    argc:

    • int
    • количество аргументов, введенных в командную строку
    • если argc = 2, все ок. Если нет, выводим инструкцию и закрываем программу.
    • Если argc = 2, проверяем, является ли ключ целочисленным.
    • argv — это массив строк, список с введенными в него аргументами.

    Как вы уже знаете из лекции, массив — структура данных, содержащая разные данные одного типа в последовательно идущих ячейках.

    Задание 1. Написать шифр Цезаря - 1

    Например, пользователь ввел строку blastoff Team Rocket, тогда:

    Задание 1. Написать шифр Цезаря - 2

    Переводим с помощью функции atoi() полученное в целое число. Если это невозможно, функция вернет 0. 

    Задание 1. Написать шифр Цезаря - 3
  2. Запрос у пользователя текста. Это просто: всё, что вводит пользователь, является строкой. 
  3. Шифрование. Алгоритм прост, но как пояснить компьютеру, какие буквы идут одна за другой? Самое время вспомнить о таблице ASCII!
    cs50 ascii chart

Однако в строке могут быть не только буквы… Прежде, чем перейти к изменению строк, представьте, что нужно поменять только один символ. Мы хотим сменить буквы из начального текста, а не знаки или цифры. Что мы должны сделать? Для начала нам нужно проверить, есть ли этот символ в алфавите. Это можно сделать с помощью функции isalpha()

Если символ входит в алфавит, эта функция возвращает значение true и false во всех других случаях. Еще две полезные функции — isupper () и islower() возвращают true в случае, если буква прописная или строчная соответственно. Таким образом: 

Isalpha(‘Z’) -> true
Isalpha(‘;’) -> false
Isupper(‘Z’) ->true
Isupper(‘z’) -> false
Islower(‘Z’) -> false
Islower(‘z’)->true

Если isalpha возвращает true, нам нужно поменять этот символ с помощью ключа. 
Рассмотрим и разберем в качестве примера программу Замайлы, ассистента CS50

/*
 * asciimath.c
 * by Zamyla Chan
 *
 * Calculates the addition of a char and an integer,
 * and displays both the resultant character and its
 * ASCII value.
 *
 * Usage: ./asciimath key [char]
 *
 */

#include 
      
  
      
  
      
  
    #include 
   
     #include 
    
      int main(int argc, string argv[]) {     if (argc != 2)         {   printf("print the key next time \n");     return 1;         }     // key is the second command line argument       int key = atoi(argv[1]); //преобразование строки в int     int letter = 'A';         printf("\nCalculating '%c' + %d...\n", letter, key);         int result = (letter + key);             printf("The ASCII value of %c is %d.\n\n", result, result);         return 0; } 
     
    
  

Вас может удивить, почему ‘A’ — это целое число, тогда как она явно является буквой. Оказывается символы и целые числа — взаимозаменяемы. Поставив букву A в одиночные кавычки можно получить её ASCII-код в int. Будьте внимательны: вам нужны именно одинарные кавычки, без них компилятор будет искать переменную по имени A, а не символ. 

Затем в строке

int result = (letter + key);

мы прибавляем значение ключа к ASCII-коду буквы и сохраняем их в переменной целого типа. Даже если результат имеет тип int, оператор printf использует плейсхолдер для символов. Таким образом, программа печатает символ, связанный с целочисленным результатом. Во втором случае мы выводим на экран число с помощью плейсхолдера %d

Вы можете ввести этот код в сs50 IDE и поиграться с ним.

Проверим работу asciimath для разных ключей. Возьмем значение 25, увидим следующую картинку: 

Задание 1. Написать шифр Цезаря - 4

А теперь пусть ключ будет 26: 

Задание 1. Написать шифр Цезаря - 5

Мы получили [, а вовсе не букву A. Это просто следующий символ ASCII после Z. Так что простое прибавление ключа работать не будет. Нам нужно использовать формулу шифра, чтобы возвращаться в начало алфавита как только буквы закончатся. 
Помните, мы уже писали выше:

ci = (pi + k) % 26

Где ci — буква номер i в шифрованном тексте, pi — буква номер i в незашифрованном тексте, k — ключ, а %26 — остаток от деления на 26 (или «деление по модулю 26»). 

Давайте применим эту формулу для буквы Y. Возьмем k = 2. Посчитаем (‘Y’ + 2) %26

ASCII-код буквы ‘Y’= 89. Тогда

(‘Y’ + 2) %26 = (89 + 2)%26 = 91%26 = 13 

Но это вовсе не ASCII-значение нужной нам буквы A, которое равно 65. 

Теперь давайте придадим каждой букве алфавита значение от 0 до 25 по порядку. В таком случае Y = 24. 
(24+2)%26 = 0

Буква А как раз имеет такой индекс. Таким образом, эта формула относится к алфавитному индексу букв, а не их ASCII-значений. 

Для печати зашифрованного символа вам нужно будет его ASCII-значение. И разберитесь с тем, как переключаться между ASCII-значением и номером в алфавите.

После того, как мы выяснили формулу для одного символа, нужно применить её для каждой буквы во вводимой с клавиатуры строке. Но только если это буквы!

И помните, для больших и малых букв нужны разные значения. Тут пригодятся функции isupper и islower. У вас может быть две формулы, одна для больших букв, другая — для малых, функции помогут выбрать, какую из них применить. 

Как применить формулу к каждому отдельному символу в строке? Помним, что строка — это просто массив символов. Определить количество итераций в цикле поможет функция strlen (длина строки). 

Задание 1. Написать шифр Цезаря - 6
Комментарии (74)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Hennadii Nosach Уровень 1
10 января 2023

#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

int k; //вводим глобальную переменную k

int get_encrypt_char(char decrypt_char); //объявляем прототип функции для шифрования

int main(int argc, string argv[])
{
    if (argc!=2) //проверка ключа
    {
        printf("Enter correct key next time\n");
        return 1;
    }

    k = atoi(argv[1]); //конвертируем аргумент argv в int

    printf("plaintext: ");
    string text = get_string(""); //вводим текст для шифрования
    int lenght = strlen(text); //вводим переменную длины строки для шифрования
    printf("ciphertext: ");

    for (int i = 0; i < lenght; i++) //цикл для проверки каждого символа строки
    {
        printf("%c", get_encrypt_char(text[i])); //вывод шифра с помощь ф-ции для шифрования
    }
    printf("\n");
    return 0;

}

   int get_encrypt_char(char decrypt_char) //создаем функцию для шифрования по номеру ASCII
{
    if (isalpha(decrypt_char)) //условие принадлежности незашифрованных символов к алфавиту
    {
        int ascii_start_number = isupper(decrypt_char) ? 65 : 97; //проверка символа на регистр и присваивание номера ascii с помощью тернарного оператора
        return (((decrypt_char % ascii_start_number) + k) %26) + ascii_start_number; //возвращаем зашифрованный символ с помощью формулы
    }
    else
    {
        return decrypt_char; //иначе возвращаем незашифрованный символ 
    }
}

12 сентября 2022
#include <stdio.h> #include <cs50.h> #include <stdlib.h> #include <ctype.h> #include <string.h> int main(int argc, string argv[]) { if (argc != 2) { printf("Введите 2 аргумента командной строки \n"); return 1; } for (int i = 0, n = strlen (argv[1]); i < n; i++) { if (isdigit (argv[1][i])) ; else { printf ("Введите в аргументе целое число\n"); return 1; } } int key = atoi(argv[1]); string text = get_string ("Введите текст\n"); for (int i = 0, n = strlen (text); i < n; i++) { if (isalpha (text[i])) { if (isupper (text[i])) { int results = (((text[i] - 65) + key) %26) + 65; printf ("%c", results); } else { int results = (((text[i] - 97) + key) %26) + 97; printf ("%c", results); } } else printf ("%c", text[i]); } printf ("\n"); return 0; } Гляньте кто шарит
FeatHonnar Уровень 16
15 сентября 2022
Можешь подсказать, как организовать "переключение" между алфавитом и ASCII?
Павел Зуев Уровень 1
9 сентября 2022

#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

int main(int argc,string argv[])
{
    if (argc != 2)
    {
    printf ("Ввод не соответсвует условию, попробуйте запустить программу еще раз \n");
    return 1;
    }
    int k = atoi(argv[1]);
    string a = get_string("Введите текст для шифрования:\n");
    for (int i=0, b=strlen(a); i < b; i++)
    {
        if (isalpha(a[i])) //проверка символ ли это алфавита
        {
            if (isupper (a[i])) //проверка на верхний регистр
            {
                int x = a[i];       // присваиваем иксу порядковый номер символа ASCII той буквы которая идет в массиве
                int y = x - 65;     // переводим номер ASCII в порядковый номер по алфавиту
                int z = (y+k)%26;   // вычесляем порядковый номер по алфавиту зашифрованной буквы от 0 до 25
                x = z + 65;         // переходим от порядкового номера кодированной буквы к номеру ASCII
                printf("%c", x);
            }
            else
            {
                int x = a[i];       // присваиваем иксу порядковый номер символа ASCII той буквы которая идет в массиве
                int y = x - 97;     // переводим номер ASCII в порядковый номер по алфавиту
                int z = (y+k)%26;   // вычесляем порядковый номер по алфавиту зашифрованной буквы от 0 до 25
                x = z + 97;         // переходим от порядкового номера кодированной буквы к номеру ASCII
                printf("%c", x);
            }
        }
        else
        {
        printf("%c", a[i]); // вывод символа
        }
    }
printf("\n");
}

FeatHonnar Уровень 16
16 сентября 2022
Помог
6 сентября 2022
До сих пор не могу понять, зачем это делать через аргументы командной строки, ибо первый запуск от пользователя будет некорректным. Написал стандартными средствами, включая библиотеку ctype.h //шифр Цезаря, в котором перед запуском командной строки пользователю нужно ввести 2 аргумента #include <cs50.h> #include <stdio.h> #include <string.h> #include <ctype.h> int main(void) { int k; //secretkey do { printf ("Input secretkey (positive integer):\n"); k = get_int(""); } while (k < 1); printf ("Input plaintext:\n"); string plaintext = get_string(""); printf ("Incipher...\n"); printf ("Your ciphertext is: \n"); for (int i = 0, n = strlen(plaintext); i < n; i++) { if (isalpha(plaintext[i])) //if char is letter { if (islower(plaintext[i])) //if letter is lower { printf ("%c", (plaintext[i] - 97 + k) % 26 + 97); } if (isupper(plaintext[i])) // if letter is upper { printf ("%c", (plaintext[i] - 65 + k) % 26 + 65); } } else // if char is not letter { printf ("%c", plaintext[i]); //if char is not letter } } printf ("\n"); }
6 сентября 2022
~/pset2/ $ ./caeser Input secretkey (positive integer): 3 Input plaintext: cat DOG XYZ Incipher... Your ciphertext is: fdw GRJ ABC
Timosha Google Уровень 3
20 июня 2022

#include <stdio.h>
#include <stdlib.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>
int main(int argc, string argv[]) {

    if (argc != 2) {
    printf("Enter the key once\n");
        return 1;
    }
        printf("\n");
        int key = atoi(argv[1]);
        printf("Enter the text: \n");
        string p = GetString();
        for (int i=0, n=strlen(p); i<n; i++) {
            if (isalpha(p[i])) {
                int letter = p[i];
                int result;
                if (isupper(letter)) {
                    letter = letter -64;
                    result = (letter + key) % 26;
                    result = result +64;
                    }
                if (islower(letter)) {
                    letter = letter -96;
                    result = (letter + key) % 26;
                    result = result + 96;
                }

                printf("%c", result);

            } else {
                printf("%c", p[i]);
                }

        }
    printf("\n");



}
Kamil Khakimov Уровень 2
13 мая 2022
Я так понял правильного написания программы нет. Сам сначала, делаю , потом смотрю , что сделали другие. У всех разные решения одной задачи. Кстати на сайте https://cs50.harvard.edu/college/2022/spring/psets/2/caesar/ описания задачи. Там просят писать собственные функции. Мне проще конечно все в main засунуть, но видимо специально просят , что бы мы учились писать свои функции. Всем удачи.
weingeneer Уровень 1
11 февраля 2022
Некоторые решения не соответствуют условию задачи в части фильтра аргументов командной строки от ввода букв. Ссылка на актуальный кейс https://cs50.harvard.edu/college/2022/spring/psets/2/caesar/ Чекер check50 cs50/problems/2022/spring/caesar
HellishDevil Уровень 2
7 января 2022
Ребят, у вас опечатка: "Мы получили [, а вовсе не букву A". Там должно быть: "Мы получили [, а вовсе не букву Z."
mr_guk Уровень 1
20 августа 2021
Зашифрованная ссылка удалена((( че там было то?
Vadim Skorovarov Уровень 6
23 декабря 2021
она не удалена))
Karen Уровень 1
18 февраля 2022
RickRoll'D
mr_guk Уровень 1
20 августа 2021

#include <stdio.h>
#include <cs50.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main(int argc, string argv[])
{
    if (argc != 2)
    {
        printf("print the key next time\n");
        return 1;
    }
    string word = get_string("Insert your text: ");
    int key = atoi(argv[1]);
    int len = strlen(word);

    for (int i= 0;i<len; i++)
    {
        int letter =word[i];
        if (isalpha(word[i]))
        {
            int result;
            if (isupper(word[i]))
            {
                result = ((letter - 65 + key) % 26) + 65;
            }
            else
            {
                result = ((letter - 97 + key) % 26) + 97;
            }
            printf("%c", result);
        }
        else
        {
            printf("%c", word[i]);
        }
    }
    printf("\n");
    return 0;
}