diff --git a/DIRECTORY.md b/DIRECTORY.md index 2c6000c94ed4..322289cebd7e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -119,7 +119,7 @@ * [Shuffled Shift Cipher](ciphers/shuffled_shift_cipher.py) * [Simple Keyword Cypher](ciphers/simple_keyword_cypher.py) * [Simple Substitution Cipher](ciphers/simple_substitution_cipher.py) - * [Trafid Cipher](ciphers/trafid_cipher.py) + * [Trifid Cipher](ciphers/trifid_cipher.py) * [Transposition Cipher](ciphers/transposition_cipher.py) * [Transposition Cipher Encrypt Decrypt File](ciphers/transposition_cipher_encrypt_decrypt_file.py) * [Vigenere Cipher](ciphers/vigenere_cipher.py) diff --git a/ciphers/trafid_cipher.py b/ciphers/trifid_cipher.py similarity index 59% rename from ciphers/trafid_cipher.py rename to ciphers/trifid_cipher.py index 8aa2263ca5ac..fa84e513f793 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trifid_cipher.py @@ -1,135 +1,197 @@ -# https://en.wikipedia.org/wiki/Trifid_cipher -from __future__ import annotations - - -def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: - one, two, three = "", "", "" - tmp = [] - - for character in message_part: - tmp.append(character_to_number[character]) - - for each in tmp: - one += each[0] - two += each[1] - three += each[2] - - return one + two + three - - -def __decrypt_part( - message_part: str, character_to_number: dict[str, str] -) -> tuple[str, str, str]: - tmp, this_part = "", "" - result = [] - - for character in message_part: - this_part += character_to_number[character] - - for digit in this_part: - tmp += digit - if len(tmp) == len(message_part): - result.append(tmp) - tmp = "" - - return result[0], result[1], result[2] - - -def __prepare( - message: str, alphabet: str -) -> tuple[str, str, dict[str, str], dict[str, str]]: - # Validate message and alphabet, set to upper and remove spaces - alphabet = alphabet.replace(" ", "").upper() - message = message.replace(" ", "").upper() - - # Check length and characters - if len(alphabet) != 27: - raise KeyError("Length of alphabet has to be 27.") - for each in message: - if each not in alphabet: - raise ValueError("Each message character has to be included in alphabet!") - - # Generate dictionares - numbers = ( - "111", - "112", - "113", - "121", - "122", - "123", - "131", - "132", - "133", - "211", - "212", - "213", - "221", - "222", - "223", - "231", - "232", - "233", - "311", - "312", - "313", - "321", - "322", - "323", - "331", - "332", - "333", - ) - character_to_number = {} - number_to_character = {} - for letter, number in zip(alphabet, numbers): - character_to_number[letter] = number - number_to_character[number] = letter - - return message, alphabet, character_to_number, number_to_character - - -def encrypt_message( - message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 -) -> str: - message, alphabet, character_to_number, number_to_character = __prepare( - message, alphabet - ) - encrypted, encrypted_numeric = "", "" - - for i in range(0, len(message) + 1, period): - encrypted_numeric += __encrypt_part( - message[i : i + period], character_to_number - ) - - for i in range(0, len(encrypted_numeric), 3): - encrypted += number_to_character[encrypted_numeric[i : i + 3]] - - return encrypted - - -def decrypt_message( - message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 -) -> str: - message, alphabet, character_to_number, number_to_character = __prepare( - message, alphabet - ) - decrypted_numeric = [] - decrypted = "" - - for i in range(0, len(message) + 1, period): - a, b, c = __decrypt_part(message[i : i + period], character_to_number) - - for j in range(len(a)): - decrypted_numeric.append(a[j] + b[j] + c[j]) - - for each in decrypted_numeric: - decrypted += number_to_character[each] - - return decrypted - - -if __name__ == "__main__": - msg = "DEFEND THE EAST WALL OF THE CASTLE." - encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") - decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") - print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}") +from __future__ import annotations + +""" +The trifid cipher is a classical cipher invented by Félix Delastelle in 1902 + +Wikipedia : https://en.wikipedia.org/wiki/Trifid_cipher + +It takes in input the plaintext and the key and encrypts it +For decryption, the same key used in the encryption is required + +Syntax : encrypt_message(plaintext, key) + decrypt_message(encrypted, key) +""" + + +def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str: + one, two, three = "", "", "" + tmp = [] + + for character in message_part: + tmp.append(character_to_number[character]) + + for each in tmp: + one += each[0] + two += each[1] + three += each[2] + + return one + two + three + + +def __decrypt_part( + message_part: str, character_to_number: dict[str, str] +) -> tuple[str, str, str]: + tmp, this_part = "", "" + result = [] + + for character in message_part: + this_part += character_to_number[character] + + for digit in this_part: + tmp += digit + if len(tmp) == len(message_part): + result.append(tmp) + tmp = "" + + return result[0], result[1], result[2] + + +def __prepare( + message: str, alphabet: str +) -> tuple[str, str, dict[str, str], dict[str, str]]: + """__prepare validates the entered message and alphabet to check if + it satisfies all the given requirements: + + 1) message must not contain numbers like 1,2,3 etc + 2) Key must have all the alphabets the message has + 3) Length of the key has to be 27 + + >>> __prepare("hello", "ABCDEHG") + Traceback (most recent call last): + ... + KeyError: 'Length of alphabet has to be 27.' + + >>> __prepare("hello", "AABCDEFGGIJKLMNOPQRSTUVWXYZ") + Traceback (most recent call last): + ... + raise ValueError("Each message character has to be included in alphabet!") + ValueError: Each message character has to be included in alphabet! + """ + # Validate message and alphabet, set to upper and remove spaces + alphabet = alphabet.replace(" ", "").upper() + message = message.replace(" ", "").upper() + + # Check length and characters + if len(alphabet) != 27: + raise KeyError("Length of alphabet has to be 27.") + for each in message: + if each not in alphabet: + raise ValueError("Each message character has to be included in alphabet!") + + # Generate dictionares + numbers = ( + "111", + "112", + "113", + "121", + "122", + "123", + "131", + "132", + "133", + "211", + "212", + "213", + "221", + "222", + "223", + "231", + "232", + "233", + "311", + "312", + "313", + "321", + "322", + "323", + "331", + "332", + "333", + ) + character_to_number = {} + number_to_character = {} + for letter, number in zip(alphabet, numbers): + character_to_number[letter] = number + number_to_character[number] = letter + + return message, alphabet, character_to_number, number_to_character + + +def encrypt_message( + message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 +) -> str: + """ + encrypt_message(message, alphabet, period) encrypts the message using the alphabet + and period + + 1) If not provided, default alphabet is taken as "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 2) If not provided, default period is taken as 5 + 3) message is a compulsory argument + + >>> print(encrypt_message("hello world")) + BOJN.WKPOY + + >>> print(encrypt_message("how are you", "BAECDHFGIJKLMNOPRQTSUVXWZY.")) + HDM.XGULQ + + >>> print(encrypt_message("all aboard the train", "BAECDHFGIJKLMNOPRQTSUVXWZY.", 4)) + DBAYCKFXFCKIVEFON + """ + message, alphabet, character_to_number, number_to_character = __prepare( + message, alphabet + ) + encrypted, encrypted_numeric = "", "" + + for i in range(0, len(message) + 1, period): + encrypted_numeric += __encrypt_part( + message[i : i + period], character_to_number + ) + + for i in range(0, len(encrypted_numeric), 3): + encrypted += number_to_character[encrypted_numeric[i : i + 3]] + + return encrypted + + +def decrypt_message( + message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 +) -> str: + """ + decrypt_message(message, alphabet, period) decrypts the message using the alphabet + and period + + 1) If not provided, default alphabet is taken as "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 2) If not provided, default period is taken as 5 + 3) message is a compulsory argument + + >>> print(decrypt_message("BOJN.IYSS")) + HELLOGUYS + + >>> print(decrypt_message("KHQDYRSTJWPYTEAOC", "QWERTYUIOPASDFGHJKLZXCVBNM.")) + ALLABOARDTHETRAIN + + >>> print(decrypt_message("DBAYCKFXFCKIVEFON", "BAECDHFGIJKLMNOPRQTSUVXWZY.", 4)) + ALLABOARDTHETRAIN + """ + message, alphabet, character_to_number, number_to_character = __prepare( + message, alphabet + ) + decrypted_numeric = [] + decrypted = "" + + for i in range(0, len(message) + 1, period): + a, b, c = __decrypt_part(message[i : i + period], character_to_number) + + for j in range(len(a)): + decrypted_numeric.append(a[j] + b[j] + c[j]) + + for each in decrypted_numeric: + decrypted += number_to_character[each] + + return decrypted + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/financial/ABOUT.md b/financial/ABOUT.md index f6b0647f8201..3409e32ea04a 100644 --- a/financial/ABOUT.md +++ b/financial/ABOUT.md @@ -2,3 +2,4 @@ * Compound Interest: "Compound interest is calculated by multiplying the initial principal amount by one plus the annual interest rate raised to the number of compound periods minus one." [Compound Interest](https://www.investopedia.com/) * Simple Interest: "Simple interest paid or received over a certain period is a fixed percentage of the principal amount that was borrowed or lent. " [Simple Interest](https://www.investopedia.com/) +* Annual Percentage Rate: "Annual percentage rate (APR) refers to the yearly interest generated by a sum that's charged to borrowers or paid to investors. APR is expressed as a percentage that represents the actual yearly cost of funds over the term of a loan or income earned on an investment. " [Annual Percentage Rate]("https://www.investopedia.com/terms/a/apr.asp") \ No newline at end of file