diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 4427a5234..7bda51976 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,63 +1,250 @@ -def encrypt(input_string: str, key: int) -> str: +from string import ascii_letters + + +def encrypt(input_string: str, key: int, alphabet=None) -> str: + """ + encrypt + ======= + Encodes a given string with the caesar cipher and returns the encoded + message + + Parameters: + ----------- + * input_string: the plain-text that needs to be encoded + * key: the number of letters to shift the message by + + Optional: + * alphabet (None): the alphabet used to encode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + Returns: + * A string containing the encoded cipher-text + + More on the caesar cipher + ========================= + The caesar cipher is named after Julius Caesar who used it when sending + secret military messages to his troops. This is a simple substitution cipher + where very character in the plain-text is shifted by a certain number known + as the "key" or "shift". + + Example: + Say we have the following message: + "Hello, captain" + + And our alphabet is made up of lower and uppercase letters: + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + And our shift is "2" + + We can then encode the message, one letter at a time. "H" would become "J", + since "J" is two letters away, and so on. If the shift is ever two large, or + our letter is at the end of the alphabet, we just start at the beginning + ("Z" would shift to "a" then "b" and so on). + + Our final message would be "Jgnnq, ecrvckp" + + Further reading + =============== + * https://en.m.wikipedia.org/wiki/Caesar_cipher + + Doctests + ======== + >>> encrypt('The quick brown fox jumps over the lazy dog', 8) + 'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo' + + >>> encrypt('A very large key', 8000) + 's nWjq dSjYW cWq' + + >>> encrypt('a lowercase alphabet', 5, 'abcdefghijklmnopqrstuvwxyz') + 'f qtbjwhfxj fqumfgjy' + """ + # Set default alphabet to lower and upper case english chars + alpha = alphabet or ascii_letters + + # The final result string result = "" - for x in input_string: - if not x.isalpha(): - result += x - elif x.isupper(): - result += chr((ord(x) + key - 65) % 26 + 65) - elif x.islower(): - result += chr((ord(x) + key - 97) % 26 + 97) + + for character in input_string: + if character not in alpha: + # Append without encryption if character is not in the alphabet + result += character + else: + # Get the index of the new key and make sure it isn't too large + new_key = (alpha.index(character) + key) % len(alpha) + + # Append the encoded character to the alphabet + result += alpha[new_key] + return result -def decrypt(input_string: str, key: int) -> str: - result = "" - for x in input_string: - if not x.isalpha(): - result += x - elif x.isupper(): - result += chr((ord(x) - key - 65) % 26 + 65) - elif x.islower(): - result += chr((ord(x) - key - 97) % 26 + 97) - return result +def decrypt(input_string: str, key: int, alphabet=None) -> str: + """ + decrypt + ======= + Decodes a given string of cipher-text and returns the decoded plain-text + + Parameters: + ----------- + * input_string: the cipher-text that needs to be decoded + * key: the number of letters to shift the message backwards by to decode + + Optional: + * alphabet (None): the alphabet used to decode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + Returns: + * A string containing the decoded plain-text + + More on the caesar cipher + ========================= + The caesar cipher is named after Julius Caesar who used it when sending + secret military messages to his troops. This is a simple substitution cipher + where very character in the plain-text is shifted by a certain number known + as the "key" or "shift". Please keep in mind, here we will be focused on + decryption. + + Example: + Say we have the following cipher-text: + "Jgnnq, ecrvckp" + + And our alphabet is made up of lower and uppercase letters: + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + And our shift is "2" + + To decode the message, we would do the same thing as encoding, but in + reverse. The first letter, "J" would become "H" (remember: we are decoding) + because "H" is two letters in reverse (to the left) of "J". We would + continue doing this. A letter like "a" would shift back to the end of + the alphabet, and would become "Z" or "Y" and so on. + + Our final message would be "Hello, captain" + + Further reading + =============== + * https://en.m.wikipedia.org/wiki/Caesar_cipher + + Doctests + ======== + >>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8) + 'The quick brown fox jumps over the lazy dog' + + >>> decrypt('s nWjq dSjYW cWq', 8000) + 'A very large key' + + >>> decrypt('f qtbjwhfxj fqumfgjy', 5, 'abcdefghijklmnopqrstuvwxyz') + 'a lowercase alphabet' + """ + # Turn on decode mode by making the key negative + key *= -1 + + return encrypt(input_string, key, alphabet) -def brute_force(input_string: str) -> None: +def brute_force(input_string: str, alphabet=None) -> dict: + """ + brute_force + =========== + Returns all the possible combinations of keys and the decoded strings in the + form of a dictionary + + Parameters: + ----------- + * input_string: the cipher-text that needs to be used during brute-force + + Optional: + * alphabet: (None): the alphabet used to decode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + More about brute force + ====================== + Brute force is when a person intercepts a message or password, not knowing + the key and tries every single combination. This is easy with the caesar + cipher since there are only all the letters in the alphabet. The more + complex the cipher, the larger amount of time it will take to do brute force + + Ex: + Say we have a 5 letter alphabet (abcde), for simplicity and we intercepted the + following message: + + "dbc" + + we could then just write out every combination: + ecd... and so on, until we reach a combination that makes sense: + "cab" + + Further reading + =============== + * https://en.wikipedia.org/wiki/Brute_force + + Doctests + ======== + >>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20] + "Please don't brute force me!" + + >>> brute_force(1) + Traceback (most recent call last): + TypeError: 'int' object is not iterable + """ + # Set default alphabet to lower and upper case english chars + alpha = alphabet or ascii_letters + + # The key during testing (will increase) key = 1 + + # The encoded result result = "" - while key <= 94: - for x in input_string: - indx = (ord(x) - key) % 256 - if indx < 32: - indx = indx + 95 - result = result + chr(indx) - print(f"Key: {key}\t| Message: {result}") + + # To store data on all the combinations + brute_force_data = {} + + # Cycle through each combination + while key <= len(alpha): + # Decrypt the message + result = decrypt(input_string, key, alpha) + + # Update the data + brute_force_data[key] = result + + # Reset result and increase the key result = "" key += 1 - return None + + return brute_force_data def main(): while True: - print(f'{"-" * 10}\n Menu\n{"-" * 10}') + print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") - choice = input("What would you like to do?: ") - if choice not in ["1", "2", "3", "4"]: + + # get user input + choice = input("\nWhat would you like to do?: ").strip() or "4" + + # run functions based on what the user chose + if choice not in ("1", "2", "3", "4"): print("Invalid choice, please enter a valid choice") elif choice == "1": input_string = input("Please enter the string to be encrypted: ") - key = int(input("Please enter off-set between 0-25: ")) - if key in range(1, 95): - print(encrypt(input_string.lower(), key)) + key = int(input("Please enter off-set: ").strip()) + + print(encrypt(input_string, key)) elif choice == "2": input_string = input("Please enter the string to be decrypted: ") - key = int(input("Please enter off-set between 1-94: ")) - if key in range(1, 95): - print(decrypt(input_string, key)) + key = int(input("Please enter off-set: ").strip()) + + print(decrypt(input_string, key)) elif choice == "3": input_string = input("Please enter the string to be decrypted: ") - brute_force(input_string) - main() + brute_force_data = brute_force(input_string) + + for key, value in brute_force_data.items(): + print(f"Key: {key} | Message: {value}") + elif choice == "4": print("Goodbye.") break diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 327e2c67f..fe8890de9 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -84,6 +84,7 @@ def test_burkes(file_path: str = "digital_image_processing/image_data/lena_small burkes.process() assert burkes.output_img.any() + def test_nearest_neighbour( file_path: str = "digital_image_processing/image_data/lena_small.jpg", ): diff --git a/sorts/sleep_sort.py b/sorts/sleep_sort.py new file mode 100644 index 000000000..0feda9c5e --- /dev/null +++ b/sorts/sleep_sort.py @@ -0,0 +1,49 @@ +""" +Sleep sort is probably the wierdest of all sorting functions with time-complexity of +O(max(input)+n) which is quite different from almost all other sorting techniques. +If the number of inputs is small then the complexity can be approximated to be +O(max(input)) which is a constant + +If the number of inputs is large, the complexity is approximately O(n). + +This function uses multithreading a kind of higher order programming and calls n +functions, each with a sleep time equal to its number. Hence each of function wakes +in sorted time. + +This function is not stable for very large values. + +https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort +""" +from threading import Timer +from time import sleep +from typing import List + + +def sleep_sort(values: List[int]) -> List[int]: + """ + Sort the list using sleepsort. + >>> sleep_sort([3, 2, 4, 7, 3, 6, 9, 1]) + [1, 2, 3, 3, 4, 6, 7, 9] + >>> sleep_sort([3, 2, 1, 9, 8, 4, 2]) + [1, 2, 2, 3, 4, 8, 9] + """ + sleep_sort.result = [] + + def append_to_result(x): + sleep_sort.result.append(x) + + mx = values[0] + for value in values: + if mx < value: + mx = value + Timer(value, append_to_result, [value]).start() + sleep(mx + 1) + return sleep_sort.result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(sleep_sort([3, 2, 4, 7, 3, 6, 9, 1])) diff --git a/sorts/sleepsort.py b/sorts/sleepsort.py deleted file mode 100644 index 5fa688d1b..000000000 --- a/sorts/sleepsort.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Sleepsort is probably the wierdest of all sorting functions -with time-complexity of O(max(input)+n) which is -quite different from almost all other sorting techniques. -If the number of inputs is small then the complexity -can be approximated to be O(max(input)) which is a constant - -If the number of inputs is large, the complexity is -approximately O(n). - -This function uses multithreading a kind of higher order programming -and calls n functions, each with a sleep time equal to its number. -Hence each of the functions wake in sorted form. - -This function is not stable for very large values. - -https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort -""" - -from time import sleep -from threading import Timer -from typing import List - - -def sleepsort(values: List[int]) -> List[int]: - """ - Sort the list using sleepsort. - >>> sleepsort([3, 2, 4, 7, 3, 6, 9, 1]) - [1, 2, 3, 3, 4, 6, 7, 9] - >>> sleepsort([3, 2, 1, 9, 8, 4, 2]) - [1, 2, 2, 3, 4, 8, 9] - """ - sleepsort.result = [] - def append_to_result(x): - sleepsort.result.append(x) - mx = values[0] - for v in values: - if mx < v: - mx = v - Timer(v, append_to_result, [v]).start() - sleep(mx+1) - return sleepsort.result - -if __name__ == '__main__': - import doctest - doctest.testmod() - x = [3, 2, 4, 7, 3, 6, 9, 1] - sorted_x = sleepsort(x) - print(sorted_x)