Python/ciphers/simple_keyword_cypher.py

91 lines
2.8 KiB
Python
Raw Normal View History

def remove_duplicates(key: str) -> str:
"""
Removes duplicate alphabetic characters in a keyword (letter is ignored after its
first appearance).
:param key: Keyword to use
:return: String with duplicates removed
>>> remove_duplicates('Hello World!!')
'Helo Wrd'
"""
key_no_dups = ""
for ch in key:
if ch == " " or (ch not in key_no_dups and ch.isalpha()):
key_no_dups += ch
return key_no_dups
def create_cipher_map(key: str) -> dict[str, str]:
"""
Returns a cipher map given a keyword.
:param key: keyword to use
:return: dictionary cipher map
"""
# Create a list of the letters in the alphabet
alphabet = [chr(i + 65) for i in range(26)]
# Remove duplicate characters from key
key = remove_duplicates(key.upper())
offset = len(key)
# First fill cipher with key characters
cipher_alphabet = {alphabet[i]: char for i, char in enumerate(key)}
# Then map remaining characters in alphabet to
# the alphabet from the beginning
for i in range(len(cipher_alphabet), 26):
char = alphabet[i - offset]
# Ensure we are not mapping letters to letters previously mapped
while char in key:
offset -= 1
char = alphabet[i - offset]
cipher_alphabet[alphabet[i]] = char
return cipher_alphabet
def encipher(message: str, cipher_map: dict[str, str]) -> str:
"""
Enciphers a message given a cipher map.
:param message: Message to encipher
:param cipher_map: Cipher map
:return: enciphered string
>>> encipher('Hello World!!', create_cipher_map('Goodbye!!'))
'CYJJM VMQJB!!'
"""
return "".join(cipher_map.get(ch, ch) for ch in message.upper())
def decipher(message: str, cipher_map: dict[str, str]) -> str:
"""
Deciphers a message given a cipher map
:param message: Message to decipher
:param cipher_map: Dictionary mapping to use
:return: Deciphered string
>>> cipher_map = create_cipher_map('Goodbye!!')
>>> decipher(encipher('Hello World!!', cipher_map), cipher_map)
'HELLO WORLD!!'
"""
# Reverse our cipher mappings
rev_cipher_map = {v: k for k, v in cipher_map.items()}
return "".join(rev_cipher_map.get(ch, ch) for ch in message.upper())
def main() -> None:
"""
Handles I/O
:return: void
"""
message = input("Enter message to encode or decode: ").strip()
key = input("Enter keyword: ").strip()
option = input("Encipher or decipher? E/D:").strip()[0].lower()
try:
func = {"e": encipher, "d": decipher}[option]
except KeyError:
raise KeyError("invalid input option")
cipher_map = create_cipher_map(key)
print(func(message, cipher_map))
if __name__ == "__main__":
import doctest
doctest.testmod()
main()