mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-11-23 21:11:08 +00:00
60895366c0
* fix(mypy): type annotations for cipher algorithms * Update mypy workflow to include cipher directory * fix: mypy errors in hill_cipher.py * fix build errors
91 lines
2.7 KiB
Python
91 lines
2.7 KiB
Python
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 alphabet list
|
|
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()
|