diff --git a/solutions/challenge_8/README.md b/solutions/challenge_8/README.md new file mode 100644 index 000000000..cf88e83bf --- /dev/null +++ b/solutions/challenge_8/README.md @@ -0,0 +1,33 @@ +# Challenge: Palindrome Detector + +## Description + +The "Palindrome Detector" challenge involves determining if a given string +or bytes is a palindrome. +A palindrome is a word, phrase, or sequence that reads the same backward as +forward, ignoring spaces, punctuation, and case. + +Write a function that takes a string or bytes as input and returns `True` if +it is a palindrome, and `False` otherwise. + +For example: + +- The string `A man, a plan, a canal, Panama` is a palindrome. +- The string `hello` is not a palindrome. +- The bytes `b"racecar"` is a palindrome. +- The bytes `b"hello"` is not a palindrome. + +## Example + +```python +is_palindrome("A man, a plan, a canal, Panama") +# Output: True (ignoring spaces, punctuation, and case) + +is_palindrome("hello") +# Output: False (not a palindrome) + +is_palindrome(b"racecar") +# Output: True (ignoring spaces, punctuation, and case) + +is_palindrome(b"hello") +# Output: False (not a palindrome) diff --git a/solutions/challenge_8/__init__.py b/solutions/challenge_8/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/solutions/challenge_8/palindrome_detector.py b/solutions/challenge_8/palindrome_detector.py new file mode 100644 index 000000000..7bf05e4e3 --- /dev/null +++ b/solutions/challenge_8/palindrome_detector.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for checking if a given string or bytes is a palindrome. + +Module contents: + - is_palindrome: Determines if a string or bytes is a palindrome. + +Created on 09/01/2025 +Author: Semira Tesfai +""" + +import re +from typing import Union + + +def is_palindrome(s: Union[str, bytes]) -> bool: + """Determines if a given string or bytes is a palindrome. + + Parameters: + s: Union[str, bytes], the input string or bytes to be checked + + Returns -> bool: True if the input is a palindrome, False otherwise + + Raises: + TypeError: If the input is not a string or bytes + + Examples: + >>> is_palindrome("A man, a plan, a canal, Panama") + True + >>> is_palindrome("hello") + False + >>> is_palindrome(b"racecar") + True + >>> is_palindrome(b"hello") + False + """ + if not isinstance(s, (str, bytes)): + raise TypeError("Input must be a string or bytes") + + if isinstance(s, bytes): + s = s.decode("utf-8") # Decode bytes to string + + cleaned_s = re.sub(r"[^A-Za-z0-9]", "", s).lower() + return cleaned_s == cleaned_s[::-1] diff --git a/solutions/tests/challenge_8/__init__.py b/solutions/tests/challenge_8/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/solutions/tests/challenge_8/test_palindrome_detector.py b/solutions/tests/challenge_8/test_palindrome_detector.py new file mode 100644 index 000000000..f184277bd --- /dev/null +++ b/solutions/tests/challenge_8/test_palindrome_detector.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Unit tests for palindrome_detector.py + +Created on 09/01/2025 +Author: Semira Tesfai +""" + +import unittest + +from solutions.challenge_8.palindrome_detector import is_palindrome + + +class TestIsPalindrome(unittest.TestCase): + """ + Test cases for the is_palindrome function. + """ + + def setUp(self): + """ + Set up any state tied to the test execution. Invoked for every test method. + """ + self.simple_palindromes = ["racecar", "madam"] + self.complex_palindromes = ["A Santa at NASA", "A man, a plan, a canal, Panama"] + self.non_palindromes = ["hello", "This is not a palindrome"] + self.bytes_palindromes = [b"racecar", b"madam"] + self.bytes_non_palindromes = [b"hello", b"This is not a palindrome"] + + def test_simple_palindrome(self): + """ + Test that simple palindromes are correctly identified. + """ + for sp in self.simple_palindromes: + with self.subTest(sp=sp): + self.assertTrue(is_palindrome(sp)) + + def test_complex_palindrome(self): + """ + Test that complex palindromes are correctly identified. + """ + for cp in self.complex_palindromes: + with self.subTest(cp=cp): + self.assertTrue(is_palindrome(cp)) + + def test_non_palindrome(self): + """ + Test that non-palindromic words and phrases are correctly identified. + """ + for np in self.non_palindromes: + with self.subTest(np=np): + self.assertFalse(is_palindrome(np)) + + def test_bytes_palindrome(self): + """ + Test that palindromes with bytes input are correctly identified. + """ + for bp in self.bytes_palindromes: + with self.subTest(bp=bp): + self.assertTrue(is_palindrome(bp)) + + def test_bytes_non_palindrome(self): + """ + Test that non-palindromic words and phrases with bytes input are correctly identified. + """ + for bnp in self.bytes_non_palindromes: + with self.subTest(bnp=bnp): + self.assertFalse(is_palindrome(bnp)) + + def test_invalid_input(self): + """ + Test that invalid input raises the appropriate exception. + """ + with self.assertRaises(TypeError): + is_palindrome(12345) + with self.assertRaises(TypeError): + is_palindrome(None) + + def tearDown(self): + """ + Tear down any state that was previously set up with a call to setUp(). + """ + pass