diff --git a/.vscode/settings.json b/.vscode/settings.json index ee54a0333..edeb92fde 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -123,5 +123,5 @@ "source.organizeImports.ruff": "explicit" } }, - "cSpell.words": ["dcbae"] + "cSpell.words": ["dcbae", "doctests"] } diff --git a/solutions/anagram_finder.py b/solutions/anagram_finder.py index aea200903..defcd8f93 100644 --- a/solutions/anagram_finder.py +++ b/solutions/anagram_finder.py @@ -1,34 +1,66 @@ """ -Anagram Finder -This script provides a function to determine if two strings are anagrams. +Anagram Finder Module + +This module provides a function to determine if two strings are anagrams of each other. +It checks if the strings are anagrams, handling edge cases like empty strings, special characters, and case-insensitivity. + +Created on 03 01 2025 +@author: Frankline Ambetsa """ +import doctest + def is_anagram(string1: str, string2: str) -> bool: """ Check if two strings are anagrams of each other. + Anagrams are words or phrases made by rearranging the letters of another word or phrase. + This function will check for anagram status while ignoring spaces and case sensitivity. + Args: string1 (str): The first string. string2 (str): The second string. Returns: bool: True if the strings are anagrams, False otherwise. + + Raises: + ValueError: If any string is empty except when both are empty. + + >>> is_anagram("listen", "silent") + True + >>> is_anagram("evil", "vile") + True + >>> is_anagram("hello", "world") + False + >>> is_anagram("a gentleman", "elegant man") + True + >>> is_anagram("clint eastwood", "old west action") + True """ - # Normalize strings: convert to lowercase and remove spaces - string1 = "".join(string1.lower().split()) - string2 = "".join(string2.lower().split()) - # Compare sorted characters + # Defensive assertions for valid string inputs + assert isinstance(string1, str), "string1 must be a string" + assert isinstance(string2, str), "string2 must be a string" + + # If any string is empty, raise an error, unless both are empty + if string1 == "" and string2 == "": + return True # Both empty strings are considered anagrams of each other + + if string1 == "": + raise AssertionError("string1 cannot be empty") + if string2 == "": + raise AssertionError("string2 cannot be empty") + + # Remove spaces and convert to lowercase + string1 = string1.replace(" ", "").lower() + string2 = string2.replace(" ", "").lower() + + # Check if sorted strings are equal (anagram check) return sorted(string1) == sorted(string2) +# Run doctests when executed directly if __name__ == "__main__": - # Example usage - word1 = "listen" - word2 = "silent" - print(f"Are '{word1}' and '{word2}' anagrams? {is_anagram(word1, word2)}") - - word3 = "hello" - word4 = "world" - print(f"Are '{word3}' and '{word4}' anagrams? {is_anagram(word3, word4)}") + doctest.testmod() diff --git a/solutions/tests/test_anagram_finder.py b/solutions/tests/test_anagram_finder.py index bc47c39c9..99a0c8e90 100644 --- a/solutions/tests/test_anagram_finder.py +++ b/solutions/tests/test_anagram_finder.py @@ -1,5 +1,14 @@ -import unittest +""" +Unit tests for the `is_anagram` function. + +These tests ensure correct behavior of the `is_anagram` function, +including standard anagram checking, edge cases, input validation, and defensive assertions. + +Created on 03 01 2025 +@author: Frankline Ambetsa +""" +import unittest from solutions.anagram_finder import is_anagram @@ -33,9 +42,17 @@ def test_different_lengths(self): def test_empty_strings(self): """Test edge cases with empty strings.""" - self.assertTrue(is_anagram("", "")) - self.assertFalse(is_anagram("a", "")) - self.assertFalse(is_anagram("", "a")) + self.assertTrue( + is_anagram("", "") + ) # Both empty strings should be considered anagrams + with self.assertRaises(AssertionError): + is_anagram( + "a", "" + ) # A non-empty string and empty string shouldn't be anagrams + with self.assertRaises(AssertionError): + is_anagram( + "", "b" + ) # An empty string and non-empty string shouldn't be anagrams def test_special_characters(self): """Test cases with special characters and spaces.""" @@ -43,23 +60,19 @@ def test_special_characters(self): self.assertTrue(is_anagram("clint eastwood", "old west action")) self.assertFalse(is_anagram("hello world", "world hello!")) - def test_dummy(self): - """Dummy test to ensure the test suite runs without errors.""" - self.assertEqual(1, 1) # Trivial assertion to satisfy the test framework + def test_defensive_assertions(self): + """Test defensive assertions for invalid inputs.""" + with self.assertRaises(AssertionError): + is_anagram(123, "silent") # Non-string input + with self.assertRaises(AssertionError): + is_anagram("listen", 123) # Non-string input + with self.assertRaises(AssertionError): + is_anagram("a", "") # Empty string as second argument + with self.assertRaises(AssertionError): + is_anagram("", "b") # Empty string as first argument -if __name__ == "__main__": - # Run the tests and capture results - result = unittest.TextTestRunner().run( - unittest.TestLoader().loadTestsFromTestCase(TestAnagramFinder) - ) - # Print the results summary with "Failed: 0" - print("\nTest Summary:") - print(f"Tests Run: {result.testsRun}") - print(f"Failed: {len(result.failures)}") - print(f"Errors: {len(result.errors)}") - if result.wasSuccessful(): - print("All tests passed!") - else: - print("Some tests failed.") +if __name__ == "__main__": + # Run the tests + unittest.main()