Шифр Цезаря с использованием Python

Я только начинаю изучать Python, это мой собственный взгляд на Шифр цезаря просто просматривая документы Python. Я хотел бы знать, как я мог бы сделать это лучше и как бы вы это оценили. Это хорошо?

#caeser cipher
import string

message = str(input("Enter the Message you want to encrypt: "))
shift = int(input("Enter the number of letters for cipher shift: "))

def cipher(message,shift):

    encList = []
    messageLst = []
    alphabet = list(string.ascii_lowercase * 2) 

    punct = list(string.punctuation)


    for i in message:
       messageLst.append(i)

    for i in messageLst :
        for j in alphabet:
            if i == j:
                replaceChar = alphabet[alphabet.index(j)+(shift)]
                encList.append(replaceChar)
                break
            elif i == " ":
                encList.append(i)
                break
            elif i in punct:
                encList.append(i)
                break
                
    encMessage = ""
    encMessage = encMessage.join(encList)
    #print(alphabet)

    #print(messageLst)
    #print(encList)
    print("Encrypted Message is : ", encMessage)



cipher(message,shift)

2 ответа
2

Список алфавитов

Алфавит не обязательно должен быть list, вы можете просто использовать ascii_lowercase нить:

# just a copy
alphabet = string.ascii_lowercase

# two copies
alphabet = string.ascii_lowercase * 2


# alternatively
from string import ascii_lowercase as alphabet

Использование оператора по модулю

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

alphabet = string.ascii_lowercase

shift = 5

# index is in range(0, 25)
alphabet[(6 + shift) % 26]
'l'

# index would be > 25, so it loops back around
alphabet[(21 + shift) % 26]
'a'

Поиск шифра со словарем

Теперь вы могли бы использовать словарь для создания поисков заранее, чтобы вам не приходилось использовать alphabet.index:

cipher_lookup = {char: alphabet[(i + shift) % 26] for i, char in enumerate(alphabet)}

Теперь отслеживать индексы больше не нужно:

def cipher(message, shift):
    cipher_lookup = {char: alphabet[(i + shift) % 26] for i, char in enumerate(alphabet)}

    encrypted = []

    for letter in message:
        # simply look it up in the dictionary
        encrypted.append(cipher[letter])

Проверка на пунктуацию

Использовать set здесь, а не список. Тестирование членства для set/dict — это операция с постоянной времени, а не O (N), где N — длина списка:

punct = set(string.punctuation)

'a' in punct
False

'.' in punct
True

Однако на самом деле вам не нужно проверять пунктуацию. Вместо этого вы можете использовать dict.get чтобы вернуть значение из словаря, если оно есть, и вернуть букву, если его нет:

# say shift is 5
cipher_lookup.get('a', 'a')
'f'

# punctuation is not in the cipher
# so we just return that character
cipher_lookup.get('.', '.')
'.'

Предварительное определение encMessage

Вы можете просто использовать:

enc_message="".join(enc_list)

Именование переменных

Имена переменных и функций должны быть snake_case со всеми строчными буквами

Параметры функции

Разделите параметры в определениях функций пробелом:

def cipher(message, shift):

input

Вам не нужно бросать message к str, поскольку input выводит только строки

if __name__ == "__main__" Сторожить

Это позволит вам импортировать эту функцию, не выполняя программу, если вы хотите ее повторно использовать:

# This goes at the very bottom
if __name__ == "__main__":
    message = input("Enter the Message you want to encrypt: ")
    shift = int(input("Enter the number of letters for cipher shift: "))

    print(cipher(message, shift))

Вы можете положить свой message а также shift подсказки здесь также, чтобы они не выполнялись, если вы не запускаете программу.

Рефакторинг

from string import ascii_lowercase as alphabet


def cipher(message, shift):
    cipher_lookup = {char: alphabet[(i + shift) % 26] for i, char in enumerate(alphabet)}

    encrypted_message = ""

    for letter in message:
        encrypted_message += cipher_lookup.get(letter, letter)

    return encrypted_message


if __name__ == "__main__":
    message = input("Enter the Message you want to encrypt: ")
    shift = int(input("Enter the number of letters for cipher shift: "))

    print(cipher(message, shift))

Конкатенация строк

Обычно не рекомендуется использовать конкатенацию строк, но я думаю, что это хорошее начало для демонстрации концепций того, что происходит в программе. Лучшая практика, чем for цикл будет использовать выражение генератора:

encrypted_message="".join((cipher_lookup.get(letter, letter) for letter in message))

    C.Nivs сделал отличные выводы о:

    • не нужно конвертировать alphabet в список
    • с помощью оператора по модулю (%)
    • сохранение переводов в словаре
    • с использованием set/dict а также in для тестов на членство
    • именование, пробелы, ненужные приведения и инициализации
    • __main__ охранники

    Я не буду повторять их здесь, но, пожалуйста, изучите их и следуйте их советам по этим вопросам.


    Дополнительные точки проверки кода

    Инварианты цикла

    Вы написали этот двойной цикл for:

        for i in messageLst :
            for j in alphabet:
                if i == j:
                    replaceChar = alphabet[alphabet.index(j)+(shift)]
                    encList.append(replaceChar)
                    break
                elif i == " ":
                    encList.append(i)
                    break
                elif i in punct:
                    encList.append(i)
                    break
    

    Испытания i == " " а также i in punct вложены во внутренний цикл, который зацикливается for j in alphabet. Обратите внимание, что ни эти тесты, ни код, выполняемый на основе прохождения любого из этих тестов, не зависят от j. Это неэффективно.

    Рассмотрим messageLst который содержит ['z', 'e', 'b', 'r', 'a']. Первая итерация внешнего цикла

    • назначает 'z' к переменной i, а потом
    • выполняет тело этого цикла, который является внутренним циклом. Этот цикл:
      • назначать j к первой букве alphabet (ан 'a'), и с тех пор i == j ложно, переходит к проверке i это пробел, а если нет, то если i — это знак препинания. Поскольку оба значения были ложными, цикл продолжается и,
      • назначать j ко второй букве alphabet'b'), и с тех пор i == j ложно, переходит к проверке i это пробел, а если нет, то если i — это знак препинания. Поскольку оба значения были ложными, цикл продолжается и,
      • назначать j до третьей буквы alphabet'c'), и с тех пор i == j ложно, переходит к проверке i это пробел, а если нет, то если i — это знак препинания. Поскольку оба значения были ложными, цикл продолжается и,

    Обратите внимание на эти проверки для i == ' ' а также i in punct дублируются … 26 раз в случае письма 'z'!

    Если эти тесты убрать из цикла, мы можем получить более эффективную реализацию:

        for i in messageLst :
            if i == " ":
                encList.append(i)
            elif i in punct:
                encList.append(i)
            else:
                for j in alphabet:
                    if i == j:
                        replaceChar = alphabet[alphabet.index(j)+(shift)]
                        encList.append(replaceChar)
                        break
    

    Ненужное зацикливание

    Рассмотрим только внутренний цикл:

                for j in alphabet:
                    if i == j:
                        ...
                        break
    

    Этот поиск alphabet для ценностей j это равно i, и (из-за break) останавливается при первом появлении. Когда это вхождение обнаружено, j будет равно i.

    Это сложный способ написания:

                if i in alphabet:
                    j = i
                    ...
    

    На самом деле вам даже не нужно лишнее j Переменная. Вы можете просто использовать i в ... код.

                if i in alphabet:
                    replaceChar = alphabet[alphabet.index(i) + shift]
                    encList.append(replaceChar)
    

    Улучшенный код

    Исправив инвариант цикла и убрав ненужный цикл (но без применения улучшений из другого ответа), мы получим:

        for i in messageLst :
            if i in alphabet:
                replaceChar = alphabet[alphabet.index(i) + shift]
                encList.append(replaceChar)
            elif i == " ":
                encList.append(i)
            elif i in punct:
                encList.append(i)
    

    … что, безусловно, является улучшением исходного кода.

    Батареи в комплекте

    C.Nivs также улучшил конкатенацию строк, что привело к созданию выражения генератора для создания зашифрованного сообщения. Мы можем сделать даже лучше …

    Python поставляется с str.translate функция, которая выполняет подстановку по буквам в строке … именно это и делает шифр Цезаря.

    from string import ascii_lowercase as alphabet
    
    def cipher(message: str, shift: int) -> str:
        """
        Encode a message using a Caesar Cipher with a user-defined shift on
        a 26 letter lowercase alphabet.
        """
    
        shift %= len(alphabet)
        code = str.maketrans(alphabet, alphabet[shift:] + alphabet[:shift])
        return message.translate(code)
    
    if __name__ == "__main__":
        message = input("Enter the Message you want to encrypt: ")
        shift = int(input("Enter the number of letters for cipher shift: "))
    
        print(cipher(message, shift))
    

    Примечание: Я добавил """docstring""" и напечатайте подсказки cipher функция. Оба чрезвычайно полезны при разработке Python. Обязательно изучайте их.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *