Шифр Цезаря, также известный как шифр сдвига, код Цезаря — один из самых простых и наиболее широко известных методов шифрования. Он назван в честь римского полководца Гая Юлия Цезаря, использовавшего его для секретной переписки со своими генералами.
Давайте реализуем эту систему шифрования. Однако для простоты мы будем сдвигать только латинские символы по кругу.
Формат ввода
Вводится размер сдвига для шифрования.
В файле public.txt содержится текст на английском языке.
Формат вывода
В файл private.txt запишите зашифрованный текст.
Пример
Ввод
# Пользовательский ввод
3
# Содержимое файла public.txt
Hello, world!
Вывод
# Содержимое файла private.txt
Khoor, zruog!
Ввод
# Пользовательский ввод
-10
# Содержимое файла public.txt
English alphabet: ABCDEFG...XYZ
Вывод
# Содержимое файла private.txt
Udwbyix qbfxqruj: QRSTUVW...NOP
Решение
Реализация шифра Цезаря не самая сложная задача. Ее можно поделить на две стадии – фильтрация текста для того, чтобы определить нужно ли изменять букву и непосредственно манипуляция с буквой, если да.
Проблему обычно представляет только сам процесс манипуляции. Есть два основных подхода – Работа через функции chr/ord и работа через списки/словари на уровне создания алфавита подмен.
Метод работы через chr/ord довольно громоздкий и недостаточно универсальный в силу того, что коды букв в файлах на разных платформах могут очень сильно отличаться. Ко мне обращались ученики, которые протестировав программу на windows сталкивались с тем, что она не срабатывала при попытке сдать тест. Все дело в том, то они забывали указать параметр encoding при чтении файла и работали с буквами в родной для windows кодировке CP1251, где коды символов очень сильно отличаются от кодов символов родной для пайтона UTF-8. Если бы работа шла через словари и списки, то неладное заподозрили бы сильно раньше, потому что в python ‘a’ будет означать букву а именно в кодировке UTF-8.
Итак в чем состоит идея шифра Цезаря? Перебирая буквы, мы определяем нужно ли ее кодировать. Если да, то к коду буквы прибавляется число, равное сдвигу, после чего получившийся код преобразуется в букву. Так как английские буквы расположены по-порядку то таким нехитрым способом можно реализовать требуемый сдвиг всего алфавита. Единственная проблема – буквы, расположеные в конце алфавита. Дело в том, что при сдвиге буква может “уйти” из диапазона букв алфавита. Поэтому при добавлении сдвига важно контролировать, чтобы буква оставалась в диапазоне кодов, и если новое значение кода превысило код последней буквы алфавита, вычитать из кода размер алфавита, возвращая, таким образом код в начало алфавита. В этом методе есть и другая проблема – так как у нас есть отдельно прописные и строчные буквы, нам потребуется отслеживать два диапазона. Кроме того, некоторые языки(включая русский) содержат отдельные от остальных буквы.
При использовании списков или словарей, мы сильно упрощаем как сам расчет, так и контроль границ – буквы после сдвига просто не могут выйти за пределы алфавита, так как мы всегда будем получать правильный готовый результат. Но сначала нам нао подготовить две сущности – сам алфавит ‘abcdefghijklmnopqrstuvwxyz‘ и его “сдвинутую” версию. В качестве примера возьмем сдвиг из первого тестового случая. У нас это будет три. Таким образом, отрезаем три буквы от начала алфавита и переклеиваем их в конец – ‘defghijklmnopqrstuvwxyzabc‘. Теперь для кодирования нам достаточно вычислить позицию буквы в первом списке, и взять букву, с тем же порядковым номером из второго. Остается только решить проблему с заглавными буквами, ведь наш с вами алфавит не содержит заглавных букв. Эта проблема решается очень просто. При проверке на принадлежность алфавиту мы все символы приводим к нижнему регистру. а при кодировании проверяем, если буква в исходном тексте была заглавная, то переводим букву второго алфавита в верхний регистр. Мы это уже проделывали в задачах J. Транслитерация и F. Транслитерация 2.0.
Еще больше упростить задачу можно, если создать “словарь замен”. Для этого просто объединяем два списка в словарь, где ключом будет исходная буква, а значением – буква из списка заменю.
Остается учесть всего один нюанс – значение сдвига может быть больше размера словаря, или отрицательным числом. В этом случае достаточно получить модуль от деления сдвига на размер словаря.
Посмотреть код
Решение
file_in = 'public.txt'
file_out = 'private.txt'
a = ord('a')
z = ord('z')
shift = int(input()) % 26
with open(file_in, encoding='UTF-8') as file:
data = file.read()
output = ''
for i in range(len(data)):
pos = a
if a <= (code := ord(data[i].lower())) <= z:
pos = code + shift
if pos > z:
pos -= 26
elif pos < a:
pos += 26
output += chr(pos).upper() if data[i].isupper() else chr(pos)
else:
output += data[i]
# print(output)
with open(file_out, 'w', encoding='UTF-8') as file:
file.write(output)
Решение
file_in = 'public.txt'
file_out = 'private.txt'
alphabet = 'abcdefghijklmnopqrstuvwxyz'
shift = int(input()) % len(alphabet)
shifted_alphabet = alphabet[shift:] + alphabet[:shift]
output = ''
with open(file_in, encoding='UTF-8') as file:
data = file.read()
for char in data:
new_char = char
if char.lower() in alphabet:
pos = alphabet.find(char.lower())
new_char = shifted_alphabet[pos]
output += new_char.upper() if char.isupper() else new_char
# print(output)
with open(file_out, 'w', encoding='UTF-8') as file:
file.write(output)
Решение
file_in = 'public.txt'
file_out = 'private.txt'
chars = 'abcdefghijklmnopqrstuvwxyz'
shift = int(input()) % len(chars)
shifted_chars = chars[shift:] + chars[:shift]
alphabet = {key: value for key, value in zip(chars, shifted_chars)}
output = ''
with open(file_in, encoding='UTF-8') as file:
data = file.read()
for char in data:
new_char = alphabet[char.lower()] if char.lower() in alphabet else char
output += new_char.upper() if char.isupper() else new_char
# print(output)
with open(file_out, 'w', encoding='UTF-8') as file:
file.write(output)
полезное))
Это условно-полезное. Дело в том, что никто не может гарантировать, что коды символов будут именно такими на любой платформе. Есть более-менее безопасный диапазон с кодами от 0 до 127, который представлен здесь, он неизменный практически в любой кодировке, но если речь идет о национальных символах, то стоит смотреть в сторону “словарей”, а не кодов, потому что на разных платформах, одни и те же буквы могут иметь разные значения. И если перекодировать код программы на python в нужную кодировку не так уж и сложно, то переделать коды уже куда сложнее. Как итог, я бы отдавал предпочтение программам, в которых символы (алфавиты) написаны в виде букв, а не в виде кодов.
В виде текста эту часть таблицы ASCII можно посмотреть здесь
https://learn.microsoft.com/ru-ru/office/vba/language/reference/user-interface-help/character-set-0127
до меня не сразу дошло что кодировка и таблица ascii это разные вещи
имеется в виду написать словарь вида
LETTERS = {
1: ‘a’
и тд.
}
спасибо за ваш комментарий и за ссылку
буду пользоваться))))
Немного по-другому. Вариант основан на втором и третьем решении из статьи.
У нас есть изначальный алфавит и алфавит, после сдвига. Каждой букве первого соответствует буква второго.
Таким образом можно собрать два словаря:
{ ‘a’: ‘c’, ‘b’: ‘d’, … } и {‘c’: ‘a’, ‘d’: ‘b’, …}
Второй словарь пригодится для расшифровки.
ааааааа
я просто стараюсь пока что сам придумать решение, поэтому задаю вопросы, ещё не прочитав статью
спасибо что всё объясняете
думал оставить два последних абзаца на ещё одну попытку решения ))) теперь с полным знанием буду решать эту задачу последний раз))
def caesar_secret():
“””Это будет наш секрет
здесь я решаю задачу путём высчитывания правильного ord в диапазоне 65 – 90 и 97 – 122
разница с первым вариантом в том что, смещение может быть очень большим
буквы неподвижны, цифры высчитываются (грубо говоря, зациклено)
черновик взят из S_dict_v2.py”””
ascii_lowercase = ‘abcdefghijklmnopqrstuvwxyz’
ascii_uppercase = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’
shift = int(input())
# shift = 3
# shift = -10
# shift = -10000
decoded = ”
n_sign = 1
if shift < 0:
n_sign = -1
shifted_num = (abs(shift) – abs(((abs(shift) // 26) * 26))) * n_sign
with open(‘public.txt’, ‘r’, encoding=’utf-8′) as public:
for num, line in enumerate(public):
# if num == 1:
# shift = -10
for letter in line:
if letter.isalpha():
num_ascii = ord(letter)
if letter.isupper():
new_num = (num_ascii – 65) + shifted_num
if new_num >= 26:
new_num -= 26
decoded += ascii_uppercase[new_num]
else:
new_num = (num_ascii – 65 – 32) + shifted_num
if new_num >= 26:
new_num -= 26
decoded += ascii_lowercase[new_num]
else:
decoded += letter
with open(‘private.txt’, ‘w’, encoding=’utf-8′) as private:
private.write(decoded)
caesar_secret()
# Udwbyix qbfxqruj: QRSTUVW…NOP
# Khoor, zruog!
с третьей попытки решил задачу
с помощью вашего готового кода понял что не прохожу тест со значением -1000
это плюс python или нет?
Интересно узнать ваше мнение, Сергей
a = 10000 % 26 = 16
a_minus = -10000 % 26 = 10
https://silvertests.ru/GuideView.aspx?id=32160
Это скорее философский вопрос – с одной стороны, если смотреть на эту историю с позиции циклических структур, то логично, что остаток от деления по модулю должен быть положительным числом и означать смещение вправо относительно предыдущего меньшего “круглого” значения. С другой стороны мы можем рассуждать о простом остатке от деления и тогда логично, что остаток при переходе через ноль должен становится
зеркальным относительно своих положительных значений.
Единого мнения, какую из этих стратегий выбирать при реализации операции поиска остатка от деления в любом языке программирования нет. Поэтому результат операции -6 % 26 может от языка к языку отличаться.
Что касается Python, то его интерпретация мне ближе, хотя до знакомства с ним, я был склонен считать, что в данном случае отрицательный ответ правилен.
Но как только начинаешь смотреть на эту историю именно с точки зрения циклических вычислений, становишься сторонником всегда положительного результата, так как он здорово упрощает жизнь в таких вычислениях.
Условно это может быть такая задача:
Вы находитесь в круглом здании, в одном из подъездов, которые пронумерованы от 1 до 100, Больший номер расположен правее меньшего. Вы выходите из подъезда N и идете налево или направо. Назовите номер подъезда, напротив которого вы остановитесь, когда пройдете M подъездов.
В случае с циклическими вычислениями направление движения никак не влияет на результат ровно потому, что ответ всегда будет положительным и реально отображать номер подъезда. В случае математически строгого вычисления остатка, если он вдруг будет отрицательным, потребует дополнительных вычислений.
К слову, вариантов поведения этой операции больше двух. Если мне не изменяет память, то их пять. Но наиболее распространены именно два.
рано я огорчился из-за двух вариантов, хотелось бы быть готовым))
полагаю остальные три варианта совсем совсем редкие??
они будут верными?
в numlock calculator при настройке символов, разделяющих целую и дробную часть числа калькулятор перестает правильно считать: 5 / 2 = 2
Да, крайне редкие. На самом деле за каждым из них есть своя логика и ни одна из них не является универсальной, поэтому все способы верные при условии, что мы анонсируем какой из них мы используем. В одних задачах выгоден один способ вычисления остатка, в других – другой.
Единственный вывод, который можно сделать обладая этими знаниями заключается в том, что вы не можете полагаться на операцию нахождения остатка от деления при переносе из одного языка в другой.
“chr/ord ljdjkmyj громоздкий” пожалуйста измените раскладку
Спасибо, поправил.
Можете подсказать, почему моё решение на третьем тесте сваливается?
a = [“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”]
c = int(input())
with open(“public.txt”, “r”) as f:
d = f.read()
final = “”
for symbol in d:
if symbol.lower() in a:
if symbol in a:
final += a[a.index(symbol) + c]
else:
final += a[a.index(symbol.lower()) + c].capitalize()
else:
final += symbol
with open(“private.txt”, “w”) as f:
f.write(final)
Здравствуйте, попробуйте подсунуть вашему алгоритму на вход слово “text” (то что записано в файле) и установить смещение 3 или 4.
Вот еще одно решение:
shift = int(input())
with open(“public.txt”, “r”) as file:
text = file.read()
out = ”.join(
chr((ord(char) – 97 + shift) % 26 + 97)
if char.islower()
else chr((ord(char) – 65 + shift) % 26 + 65)
if char.isupper()
else char
for char in text
)
with open(“private.txt”, “w”) as file:
file.write(out)
В программировании почти всегда срабатывает правило – универсальное проигрывает в скорости частному, в то же время частное проигрывает в стоимости разработки, эксплуатации и поддержки.
Подход через chr/ord эффективен, но с ним есть засада – он довольно сильно зависит от операционной системы и языка с которыми вы работаете. Что уж говорить про кодировки, среди которых может попасться русские кодировки cp-866 или koi-8, или cp-1251, даже в UTF-8 далеко не во всех языках буквы национальных алфавитов идут подряд. И это только русские кодировки. Вашу программу будет довольно сложно переделать, например, под казахский алфавит: https://symbl.cc/ru/alphabets/kazakh/ или немецкий, финский.
Тем не менее, повторюсь еще раз, он весьма эффективен с точки зрения производительности. Поэтому там где вы точно уверены, что ваша программа будет работать исключительно с алфавитами, в которых буквы идут строго по порядку, его можно использовать.
Программа проходит все тесты кроме 7, кто может подсказать в чем может быть проблема?
from math import ceil
althabet = [
‘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’]
replace_index = int(input())
with open(‘public.txt’, encoding=”UTF-8″) as file:
text = file.read()
cycled_althabet = ceil(len(text) / len(althabet)) * althabet
result = ”
for i, char in enumerate(text):
if char.lower() in cycled_althabet:
shifr = cycled_althabet[althabet.index(char.lower()) + replace_index]
result += shifr.upper() if char.isupper() else shifr
else:
result += char
with open(‘private.txt’, ‘w’, encoding=”UTF-8″) as file:
file.write(result)
В вашем случае должно быть
помимо этого нет необходимости в enumerate потому, что i у вас нигде не используется.
вообще использование ceil и целого модуля тут не сильно оправдано. гораздо проще использовать модуль при делении для того, чтобы найти индекс в случае, если вылетели за границы алфавита.
Считая диапазон между ‘Z’ и ‘A’ через ord(), можно отвязаться и от системы, и от словарей.
Нужно гарантировать лишь что:
алфавит кодировки в правильной последовательностипорядок и количество прописных и заглавных букв одинаковы
shift = int(input())
decoded = ”
with open(‘public.txt’, encoding=’UTF-8′) as file_in:
for char in file_in.read():
if ‘A’ <= char.upper() <= ‘Z’:
start_ord = ord(‘A’) if char.isupper() else ord(‘a’)
char_new_ord = start_ord + (ord(char) – start_ord + shift) % (ord(‘Z’) – ord(‘A’) + 1)
decoded += chr(char_new_ord)
else:
decoded += char
with open(‘private.txt’, ‘w’, encoding=’UTF-8′) as file_out:
file_out.write(decoded)
Вы абсолютно правы, и даже правильно указали почти все ограничения.
Мало того, если мы работаем исключительно с упорядоченным алфавитом, расположенным непрерывно в таблице кодировки, то такой метод будет предпочтительней с точки зрения производительности. Особенно если все свести к кодам символов и не делать постоянно преобразования туда-обратно при сравнении.
Но, как всегда есть одно но. Вы не сможете реализовать такой подход для алфавитов большинства европейских стран, или, к примеру для казахского или украинского алфавитов.
Ровно по этой причине я знакомлю читателей с решением этой задачи, которое не зависит от выбранного алфавита.