diff --git a/ciphers/baconian_cipher.py b/ciphers/baconian_cipher.py index f146ba91b78f..7ddaf29ff59c 100644 --- a/ciphers/baconian_cipher.py +++ b/ciphers/baconian_cipher.py @@ -1,9 +1,13 @@ """ -Program to encode and decode Baconian or Bacon's Cipher -Wikipedia reference : https://en.wikipedia.org/wiki/Bacon%27s_cipher +Bacon's/Baconian Cipher Encoder and Decoder + + provides functions to encode and decode messages +using Bacon's/Baconian cipher with any 2 symbols, not just A & B. + +Source: https://en.wikipedia.org/wiki/Bacon's_cipher """ -encode_dict = { +ENCODE_DICT = { "a": "AAAAA", "b": "AAAAB", "c": "AAABA", @@ -33,57 +37,146 @@ " ": " ", } +DECODE_DICT = {value: key for key, value in ENCODE_DICT.items()} -decode_dict = {value: key for key, value in encode_dict.items()} - -def encode(word: str) -> str: +def encode(message: str, symbols: tuple[str, str] = ("A", "B")) -> str: """ - Encodes to Baconian cipher - - >>> encode("hello") - 'AABBBAABAAABABAABABAABBAB' - >>> encode("hello world") - 'AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB' - >>> encode("hello world!") - Traceback (most recent call last): - ... - Exception: encode() accepts only letters of the alphabet and spaces + Encode a message using Bacon's cipher. + + Parameters + ---------- + message : str + The message to encode (letters and spaces only). + symbols : tuple[str, str], optional + Custom symbols to replace A and B, by default ("A", "B"). + + Returns + ------- + str + Encoded message using the chosen symbols. + + Raises + ------ + ValueError + If the message contains invalid characters. + + Examples + -------- + >>> encode("abc") + 'AAAAA AAAAB AAABA' + >>> encode("abc", symbols=("X", "Y")) + 'XXXXX XXXXY XXYXX' + >>> encode("hi there") + 'AABBB ABAAA BAABA AABBB AABAA BAAAA AABAA' """ - encoded = "" - for letter in word.lower(): - if letter.isalpha() or letter == " ": - encoded += encode_dict[letter] + a_sym, b_sym = symbols + encoded_message = "" + + for char in message.lower(): + if char.isalpha() or char == " ": + bacon = ENCODE_DICT[char] + encoded_message += bacon.replace("A", a_sym).replace("B", b_sym) else: - raise Exception("encode() accepts only letters of the alphabet and spaces") - return encoded + raise ValueError("Message can only contain letters and spaces.") + + return encoded_message -def decode(coded: str) -> str: +def decode(cipher: str, symbols: tuple[str, str] = ("A", "B")) -> str: """ - Decodes from Baconian cipher - - >>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB") - 'hello world' - >>> decode("AABBBAABAAABABAABABAABBAB") - 'hello' - >>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB!") - Traceback (most recent call last): - ... - Exception: decode() accepts only 'A', 'B' and spaces + Decode a Bacon's cipher message. + + Parameters + ---------- + cipher : str + The encoded message using two symbols. + symbols : tuple[str, str], optional + Symbols used in the cipher, by default ("A", "B"). + + Returns + ------- + str + Decoded message. + + Raises + ------ + ValueError + If the cipher contains invalid symbols or cannot be decoded. + + Examples + -------- + >>> decode("AAAAA AAAAB AAABA") + 'abc' + >>> decode("XXXXX XXXXY XXYXX", symbols=("X","Y")) + 'abc' """ - if set(coded) - {"A", "B", " "} != set(): - raise Exception("decode() accepts only 'A', 'B' and spaces") - decoded = "" - for word in coded.split(): - while len(word) != 0: - decoded += decode_dict[word[:5]] - word = word[5:] - decoded += " " - return decoded.strip() + sym1, sym2 = symbols + unique_symbols = set(cipher.replace(" ", "")) + if unique_symbols - {sym1, sym2}: + raise ValueError(f"Cipher must contain only symbols {sym1} and {sym2}.") + candidates = [] + for mapping in [(sym1, sym2), (sym2, sym1)]: + s1, s2 = mapping + standard = cipher.replace(s1, "A").replace(s2, "B") + try: + decoded = "" + for word in standard.split(): + while word: + chunk = word[:5] + if chunk not in DECODE_DICT: + raise ValueError + decoded += DECODE_DICT[chunk] + word = word[5:] + decoded += " " + candidates.append(decoded.strip()) + except ValueError: + candidates.append(None) -if __name__ == "__main__": - from doctest import testmod + for candidate in candidates: + if candidate is not None: + return candidate + + raise ValueError("No valid decoding found.") + + +def detect_unique_symbols(cipher: str) -> tuple[str, str]: + """ + Detects the two unique symbols used in a cipher. + + Parameters + ---------- + cipher : str + Encoded message containing exactly two unique symbols. - testmod() + Returns + ------- + tuple[str, str] + The two unique symbols found in the cipher. + + Raises + ------ + ValueError + If cipher does not contain exactly two unique symbols. + + Examples + -------- + >>> detect_unique_symbols("XXXYX YXXYX") + ('X', 'Y') + """ + letters_only = [char for char in set(cipher.replace(" ", "")) if char.isalpha()] + if len(letters_only) != 2: + raise ValueError("Cipher must contain exactly two unique alphabetic symbols.") + return tuple(letters_only) + + +if __name__ == "__main__": + # Example usage + cipher_text = ( + "FEEFE EEFFF EEFEE EFFFF FEEFF EFEEE EEEFE " + "EFEEF EEEEF FEEEE EFFEF FEFEE EFFEE EEFEF EFFEF FEFEF" + ) + sym1, sym2 = detect_unique_symbols(cipher_text) + decoded_message = decode(cipher_text, symbols=(sym1, sym2)) + print(decoded_message) # Expected: 'the quick brown fox' diff --git a/computer_vision/hsv_threshold b/computer_vision/hsv_threshold new file mode 100644 index 000000000000..2986c500b0be --- /dev/null +++ b/computer_vision/hsv_threshold @@ -0,0 +1,131 @@ +""" +HSV Filter Tool + +This file lets the user to interactively select an HSV color range +from an image using trackbars and outputs the selected HSV range. + +1. Set `IMAGE_PATH` to your image file. +2. Run the script. +3. Adjust the trackbars to select the desired HSV range. +4. Enter to print the HSV lower and upper bounds. +""" + +import cv2 +import numpy as np + +#Example Path +IMAGE_PATH = r"C:\Users\username\New folder\your_image.png" + + +def load_image(path): + """ + Load an image from the specified path. + + Parameters + ---------- + path : str + Path to the image file. + + Returns + ------- + np.ndarray + Loaded BGR image. + + Raises + ------ + FileNotFoundError + If the image cannot be loaded. + """ + image = cv2.imread(path) + if image is None: + raise FileNotFoundError(f"Failed to load image at path: {path}") + return image + + +def create_hsv_trackbars(window_name): + """ + Create HSV trackbars for filtering. + + Parameters + ---------- + window_name : str + Name of the OpenCV window. + """ + cv2.createTrackbar("H Lower", window_name, 0, 179, lambda x: None) + cv2.createTrackbar("S Lower", window_name, 0, 255, lambda x: None) + cv2.createTrackbar("V Lower", window_name, 0, 255, lambda x: None) + cv2.createTrackbar("H Upper", window_name, 179, 179, lambda x: None) + cv2.createTrackbar("S Upper", window_name, 255, 255, lambda x: None) + cv2.createTrackbar("V Upper", window_name, 255, 255, lambda x: None) + + +def get_hsv_bounds(window_name): + """ + Read the HSV lower and upper bounds from the trackbars. + + Parameters + ---------- + window_name : str + Name of the OpenCV window. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + Lower and upper HSV bounds as NumPy arrays. + """ + lower_bound = np.array([ + cv2.getTrackbarPos("H Lower", window_name), + cv2.getTrackbarPos("S Lower", window_name), + cv2.getTrackbarPos("V Lower", window_name), + ]) + upper_bound = np.array([ + cv2.getTrackbarPos("H Upper", window_name), + cv2.getTrackbarPos("S Upper", window_name), + cv2.getTrackbarPos("V Upper", window_name), + ]) + return lower_bound, upper_bound + + +def main(): + """ + Main function to run the HSV filter tool. + + Opens the image, creates trackbars, shows filtered results, + and prints the final HSV bounds when Enter is pressed. + """ + try: + image = load_image(IMAGE_PATH) + except FileNotFoundError as e: + print(e) + return + + window_name = "HSV Filter" + cv2.namedWindow(window_name) + create_hsv_trackbars(window_name) + + while True: + hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) + + lower, upper = get_hsv_bounds(window_name) + + mask = cv2.inRange(hsv_image, lower, upper) + filtered_result = cv2.bitwise_and(image, image, mask=mask) + + cv2.imshow("Original", image) + cv2.imshow("Filtered", filtered_result) + + key = cv2.waitKey(1) & 0xFF + if key == 13: # Enter key + print(f"HSV Range (Lower): {lower}") + print(f"HSV Range (Upper): {upper}") + print( + f"CSV Format: {lower[0]},{lower[1]},{lower[2]}," + f"{upper[0]},{upper[1]},{upper[2]}" + ) + break + + cv2.destroyAllWindows() + + +if __name__ == "__main__": + main() diff --git a/geometry/geometry.py b/geometry/geometry.py index a0be8eb3befc..fd3fb2c500ef 100644 --- a/geometry/geometry.py +++ b/geometry/geometry.py @@ -103,9 +103,11 @@ def area(self) -> float: def perimeter(self) -> float: """ >>> Ellipse(5, 10).perimeter - 47.12388980384689 + 48.44222344723793 """ - return math.pi * (self.major_radius + self.minor_radius) + a, b = self.major_radius, self.minor_radius + # uses ramanujans approximation + return math.pi * (3 * (a + b) - ((3 * a + b) * (a + 3 * b)) ** 0.5) class Circle(Ellipse): diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 9ec3d5384f38..63b1f16a45cc 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -63,7 +63,7 @@ def bubble_sort_recursive(collection: list[Any]) -> list[Any]: Examples: >>> bubble_sort_recursive([0, 5, 2, 3, 2]) [0, 2, 2, 3, 5] - >>> bubble_sort_iterative([]) + >>> bubble_sort_recursive([]) [] >>> bubble_sort_recursive([-2, -45, -5]) [-45, -5, -2]