diff --git a/solutions/is_palindrome.py b/solutions/is_palindrome.py new file mode 100644 index 000000000..69ade6887 --- /dev/null +++ b/solutions/is_palindrome.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +A module for checking if a string is a palindrome. + +Module contents: + - is_palindrome: checks whether a given string is a palindrome, + ignoring spaces, punctuation, and case differences. + +Created on 2024-12-30 +@author: Huda Alamassi +""" + +import unicodedata + + +def is_palindrome(text_to_check: str) -> bool: + """ + + The function takes in a string and returns True if the string is a palindrome + (reads the same backward as forward ignoring spaces, punctuation, + and case differences) and False otherwise. + + + Arguments: + text_to_check (str): The input string to be checked. + + Returns: + bool: + - True if the string is a palindrome after removing spaces, punctuation, + and case differences. + - False if the string is not a palindrome or contains only spaces/punctuation. + + Raises: + AssertionError: If the input is not a string. + + Examples: + >>> is_palindrome("radar") + True + + >>> is_palindrome("hello") + False + + >>> is_palindrome("A man, a plan, a canal, Panama!") + True + + >>> is_palindrome("No 'x' in Nixon") + True + + """ + # validate input type + assert isinstance(text_to_check, str), "text_to_check must be a string" + + # ensure case-insensitivity + text_to_check = text_to_check.lower() + + # whitespace should not impact the palindrome check + text_to_check = "".join(text_to_check.split()) + + # punctuation should not impact the palindrome check + cleaned_text = "" + for text_char in text_to_check: + if not unicodedata.category(text_char).startswith("P"): + cleaned_text += text_char + + # exclude empty or non-informative strings + if cleaned_text == "": + return False + + reversed_text = cleaned_text[::-1] + + return reversed_text == cleaned_text diff --git a/solutions/tests/test_is_palindrome.py b/solutions/tests/test_is_palindrome.py new file mode 100644 index 000000000..3fe931361 --- /dev/null +++ b/solutions/tests/test_is_palindrome.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Test module for is_palindrome function. +Contains tests for checking palindrome functionality with spaces, punctuation, and other edge cases. + +Test categories: + - Functionality tests: checking if the function correctly identifies palindromes + and non-palindromes + - Palindrome tests: typical palindromes with various punctuations and spaces + - Non-palindrome tests: strings that are not palindromes, even with punctuation and spaces + - Edge cases: empty strings, strings with only spaces, single, double characters + and and case insensitivity + - Defensive tests: incorrect input types such as booleans and non-string values + + +Created on 2024-12-30 +Author: Huda Alamassi +""" + +import unittest + +from solutions.is_palindrome import is_palindrome + + +class TestIsPalindrome(unittest.TestCase): + """Test the is_palindrome function.""" + + def test_palindrome_string(self): + """it should return True if you pass a palindrome string.""" + actual = is_palindrome("radar") + self.assertEqual(actual, True) + + def test_non_palindrome_string(self): + """it should return False if you pass a non-palindrome string.""" + actual = is_palindrome("hello") + self.assertEqual(actual, False) + + def test_ignore_spaces_and_punctuation(self): + """It should return True for a palindrome string that includes spaces and punctuation.""" + actual = is_palindrome("A man, a plan, a canal, Panama!") + self.assertEqual(actual, True) + + def test_ignore_spaces_and_punctuation_2(self): + """It should return True for another palindrome string with spaces and punctuation.""" + actual = is_palindrome( + "Are we not pure? “No, sir!” Panama’s moody Noriega brags. " + "“It is garbage!” Irony dooms a man—a prisoner up to new era." + ) + self.assertEqual(actual, True) + + def test_empty_string(self): + """it should return False if you pass an empty string""" + actual = is_palindrome("") + self.assertEqual(actual, False) + + def test_space_string(self): + """it should return False if you pass a string containing space""" + actual = is_palindrome(" ") + self.assertEqual(actual, False) + + def test_single_character(self): + """it should return True if you pass a single character""" + actual = is_palindrome("a") + self.assertEqual(actual, True) + + def test_two_identical_characters(self): + """it should return True if you pass two-character string with identical characters""" + actual = is_palindrome("aa") + self.assertEqual(actual, True) + + def test_string_with_identical_punctuation(self): + """it should return False if you pass two identical punctuation characters""" + actual = is_palindrome("!!") + self.assertEqual(actual, False) + + def test_string_with_different_punctuation(self): + """it should return False if you pass strings with different punctuation marks.""" + actual = is_palindrome("!?") + self.assertEqual(actual, False) + + def test_case_insensitivity(self): + """It should return True for a palindrome string regardless of letter case.""" + actual = is_palindrome("Level") + self.assertEqual(actual, True) + + # test defensive assertions + def test_defensive_assertion_for_integer_input(self): + """It should raise an AssertionError if the input is not a string.""" + with self.assertRaises(AssertionError): + is_palindrome(123321)