Как я могу эффективно решить эту задачу средней школы C ? (Шифр Цезаря)

#c

Вопрос:

caesar.in содержит в первой строке текст (максимум 256 символов), во второй строке число, представляющее модификацию каждой буквы ( Я должен использовать шифр Цезаря, чтобы зашифровать или расшифровать каждую букву в зависимости от номера во 2-й строке и поместить ее в dbftbs.out. Мой код работает, но он настолько несовершенен. Как я могу сделать это более эффективным?

 #include <fstream>
#include <iostream>
#include <cstring>
#include <sstream>

using namespace std;
char s[257], crypt[8];
int key,i,l;
int main()
{
    ifstream fi("caesar.in");
    fi.get(s, 257);
    fi >> key;
    fi.get();
    fi.get(crypt, 8);
    l = strlen(s);
    if (crypt[0] == 'e')
        for (i = 0; i <= l - 1; i  ) {
            if ((int(s[i]) >= 97) amp;amp; (int(s[i]) <= 122))
                if ((s[i]   key) > 122)
                    s[i] = 97   ((s[i]   key) - 123);
                else
                    s[i] = s[i]   key;
            else if ((int(s[i]) >= 65) amp;amp; (int(s[i]) <= 90))
                if ((s[i]   key) > 90)
                    s[i] = 65   ((s[i]   key) - 91);
                else
                    s[i] = s[i]   key;
        }
    else
        for (i = 0; i <= l - 1; i  ) {
            if ((int(s[i]) >= 97) amp;amp; (int(s[i]) <= 122))
                if ((s[i] - key) < 97)
                    s[i] = 123 - (97 - (s[i] - key));
                else
                    s[i] = s[i] - key;
            else if ((int(s[i]) >= 65) amp;amp; int(s[i]) <= 90)
                if ((s[i] - key) < 65)
                    s[i] = 91 - (65 - (s[i] - key));
                else
                    s[i] = s[i] - key;
        }

    ofstream fo("dbftbs.out");
    fo << s;
    fi.close();
    fo.close();
    return 0;

}

 

Комментарии:

1. лучше использовать символы вместо магических чисел .. 'a' вместо 97 и т. Д.

2. Почему вы думаете, что ваш код слишком медленный? Как вы измеряете? Вы провели профилирование, чтобы найти узкое место?

3. Я использую веб-сайт для решения школьных проблем. И за эту проблему я получаю 60/100. Ограничение по времени должно составлять 0,1 с, а используемая память 64 МБ/8 МБ

4. Подсказка: Шифр Цезаря использует оператор остатка % и смещение. Поищите в Интернете «Шифр Цезаря C » для примеров.

5. Если символ isupper , то зашифрованный символ = (((символ — ‘A’) ключ) % 26) ‘A’; Попробуйте.

Ответ №1:

Я не могу представить себе никаких проблем со скоростью вашего кода.

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


Проблема в этом вопросе заключается в потенциальном переполнении. Так что нам нужно с этим разобраться.

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

  • Таким образом, с «def» и ключом=1, зашифрованная строка будет «efg».
  • И расшифровка с ключом=1 снова переместит его влево. Результат: «def»

Мы можем наблюдать, что нам просто нужно сдвинуться на -1, поэтому отрицательная часть ключа.

Таким образом, в основном шифрование и дешифрование могут выполняться с помощью одной и той же процедуры. Нам просто нужно перевернуть клавиши.

Давайте теперь рассмотрим проблему переполнения. На данный момент мы начнем только с прописных букв. Символы имеют соответствующий код. Например, в ASCII буква «А» кодируется 65, «В» — 66 и так далее. Поскольку мы не хотим вычислять с таким числом, мы нормализуем их. Мы просто вычитаем » А » из каждого символа. Затем

  • «А» — «А» = 0
  • ‘B’ — ‘A’ = 1
  • ‘C’ — ‘A’ = 2
  • ‘D’ — ‘A’ = 3

You see the pattern. If we want to encrypt now the letter ‘C’ with key 3, we can do the following.

‘C’ — ‘A’ 3 = 5 Then we add again ‘A’ to get back the letter and we will get 5 ‘A’ = ‘F’

That is the whole magic.

But what to do with an overflow, beyond ‘Z’. This can be handled by a simple modulo division.

Let us look at ‘Z’ 1. We do ‘Z’ — ‘A’ = 25, then 1 = 26 and now modulo 26 = 0 then plus ‘A’ will be ‘A’

And so on and so on. The resulting Formula is: (c-‘A’ key)& ‘A’

Next, what with negative keys? This is also simple. Assume an ‘A’ and key=-1

Результатом будет «Z». Но это то же самое, что сдвинуть 25 вправо. Таким образом, мы можем просто преобразовать отрицательный ключ в псойтивный сдвиг. Простое утверждение будет:

 if (key < 0)  key = (26   (key % 26)) % 26;
 

А затем мы можем вызвать нашу функцию преобразования с помощью простой лямбды. Одна функция для шифрования и расшифровки. Просто с помощью перевернутого ключа.

Пожалуйста, смотрите ниже одно (из многих возможных) решение на C , в котором используется эта схема. И, пожалуйста, обратите внимание. Все преобразование, шифрование и дешифрование, выполняется только с помощью ОДНОГО std::transform оператора.

 #include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <iterator>
#include <iomanip>
#include <cctype>

const std::string inFileName{ "r:\ceasar.in" };
const std::string outFileName{ "r:\dbftbs.out" };

int main() {

    // Open the files and check, if they could be opened
    if (std::ifstream inputStream{ inFileName }; inputStream) {
        if (std::ofstream outputStream{ outFileName }; outputStream) {

            // Here we store intermediate information from the source file
            std::string text{}, command{};
            int key{};

            // Now, read data from the input file
            if (std::getline(std::getline(inputStream, text) >> key >> std::ws, command)) {

                // We need to check, if we have a valid command
                bool validCommand{};

                if (command == "encrypt") {
                    validCommand = true;
                }
                else if (command == "decrypt") {
                    // Revert the key
                    key = -key;
                    validCommand = true;
                }
                else std::cerr << "nError. Invalid command'" << command << "'nn";

                // If the command was valid
                if (validCommand) {
                    // Then do the transformation
                    // Handle negative keys
                    if (key < 0)  key = (26   (key % 26)) % 26;
                    // Transform and write to file 
                    std::transform(text.begin(), text.end(), std::ostream_iterator<char>(outputStream), [amp;key](const char c) {
                        char r{ c }; if (std::isalpha(c)) { if (std::isupper(c)) r = (c - 'A'   key) % 26   'A'; else r = (c - 'a'   key) % 26   'a'; } return r; });
                }
            }
            else std::cerr << "nError. Invalid data in input filen";
        }
        else std::cerr << "nError. Could not open input file '" << inFileName << "'nn";
    }
    else std::cerr << "nError Could not open output file '" << outFileName << "'nn";
}
 

ПРАВКА: Некоторые дополнительные сведения, если вы работаете с представлением символов ASCII. Пожалуйста, взгляните на любую таблицу ASCII. Вы увидите, что любой прописной и строчный символы отличаются на 32. Или, если вы посмотрите в двоичном формате:

 char  dez  bin           char  dez  bin
'A'   65   0100 0001     'a'   97   0110 0001 
'B'   66   0100 0010     'b'   98   0110 0010 
'C'   67   0100 0011     'b'   99   0110 0011 
 . . .
 

Итак, если вы уже знаете, что символ альфа, то единственная разница между прописными и строчными буквами-это бит номер 5. Если мы хотим знать, если char в нижнем регистре, мы можем получить это, замаскировав этот бит. c amp; 0b0010 0000 это равно c amp; 32 или c amp; 0x20 .

Если мы хотим оперировать либо прописными, либо строчными символами, мы можем замаскировать «регистр». С c amp; 0b00011111 помощью или c amp; 31 или c amp; 0x1F мы всегда получим эквиваленты для символов верхнего регистра, уже нормализованных для начала с одного.

 char  dez  bin        Masking         char  dez  bin         Masking
'A'   65   0100 0001  amp; 0x1b = 1      'a'   97   0110 0001   amp; 0x1b = 1
'B'   66   0100 0010  amp; 0x1b = 2      'b'   98   0110 0010   amp; 0x1b = 2
'C'   67   0100 0011  amp; 0x1b = 3      'b'   99   0110 0011   amp; 0x1b = 3
 . . .
 

Итак, если мы используем альфа — символ, маскируем его и вычитаем 1, то в результате получим 0..25 для любого символа верхнего или нижнего регистра.


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

 Shifting by  -1  is same as shifting by   25
Shifting by  -2  is same as shifting by   24
Shifting by  -3  is same as shifting by   23
Shifting by  -4  is same as shifting by   22
 

Итак,совершенно очевидно, что мы можем вычислить всегда положительный ключ с помощью: 26 key . Для отрицательных ключей это даст нам вышеуказанные смещения.

А для позиционных ключей у нас будет переполнение более 26, которое мы можем устранить делением по модулю 26:

 'A'-->  0   26 = 26    26 % 26 = 0 
'B'-->  1   26 = 27    27 % 26 = 1 
'C'-->  2   26 = 28    28 % 26 = 2 
'D'-->  3   26 = 29    29 % 26 = 3
 

—> > (c key) % 26 устранит переполнения и приведет к правильному новому символу en/decryptd.

And, if we combine this with the bove wisdom for negative keys, we can write: ((26 (key&))&) which will work for all positive and negative keys.

Combining that with that masking, could give us the following program:

 const char potentialLowerCaseIndicator = c amp; 0x20;
const char upperOrLower = c amp; 0x1F;
const char normalized = upperOrLower - 1;
const int withOffset =  normalized   ((26 (key&))&);
const int withOverflowCompensation = withOffset % 26;
const char newUpperCaseCharacter = (char)withOverflowCompensation   'A';
const char result = newUpperCaseCharacter | (potentialLowerCaseIndicator );
 

Конечно, все вышеперечисленные многие утверждения могут быть преобразованы в одну Лямбду:

 auto conv=[amp;](char c){return std::isalpha(c)?(char)((((camp;31)-1 ((26 (key&))&))& 65)|camp;32):c;};

// And the transformation with:
std::transform(text.begin(),text.end(),std::ostream_iterator<char>(outputStream),conv);
 

И вся, еще более сжатая программа:

 #include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <iterator>
#include <iomanip>
#include <cctype>

const std::string inFileName{ "r:\ceasar.in" };
const std::string outFileName{ "r:\dbftbs.out" };

int main() {

    // Open the files and check, if they could be opened
    if (std::ifstream inputStream{ inFileName }; inputStream) {
        if (std::ofstream outputStream{ outFileName }; outputStream) {

            // Here we store intermediate information from the source file
            std::string text{}, command{};
            int key{};

            // Now, read data from the input file
            if (std::getline(std::getline(inputStream, text) >> key >> std::ws, command)) {

                // Lambda / Function for Ceasar encryption/decryption
                auto conv=[amp;](char c){return std::isalpha(c)?(char)((((camp;31)-1 ((26 (key&))&))& 65)|camp;32):c; };
                auto doJob=[amp;](bool r){if (r)key=-key;std::transform(text.begin(),text.end(),std::ostream_iterator<char>(outputStream),conv); };

                // Check parameter
                if (command == "encrypt")       doJob(false);
                else if (command == "decrypt")  doJob(true);
                else std::cerr << "nError. Invalid command'" << command << "'nn";
            }
            else std::cerr << "nError. Invalid data in input filen";
        }
        else std::cerr << "nError. Could not open input file '" << inFileName << "'nn";
    }
    else std::cerr << "nError Could not open output file '" << outFileName << "'nn";
}
 

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