Note: Click on "Kernel" > "Restart Kernel and Run All" in JupyterLab after finishing the exercises to ensure that your solution runs top to bottom without any errors. If you cannot run this file on your machine, you may want to open it in the cloud .
The exercises below assume that you have read the first part of Chapter 6.
The ...
's in the code cells indicate where you need to fill in code snippets. The number of ...
's within a code cell give you a rough idea of how many lines of code are needed to solve the task. You should not need to create any additional code cells for your final solution. However, you may want to use temporary code cells to try out some ideas.
Palindromes are sequences of characters that read the same backward as forward. Examples are first names like "Hannah" or "Otto," words like "radar" or "level," or sentences like "Was it a car or a cat I saw?"
In this exercise, you implement various functions that check if the given arguments are palindromes or not. We start with an iterative implementation and end with a recursive one.
Conceptually, the first function, unpythonic_palindrome()
, is similar to the "Is the square of a number in [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]
greater than 100
?" example in Chapter 4 : It assumes that the
text
argument is a palindrome (i.e., it initializes is_palindrom
to True
) and then checks in a for
-loop if a pair of corresponding characters, forward
and backward
, contradicts that.
Q1: How many iterations are needed in the for
-loop? Take into account that text
may contain an even or odd number of characters! Inside unpythonic_palindrome()
below, write an expression whose result is assigned to chars_to_check
!
< your answer >
Q2: forward_index
is the index going from left to right. How can we calculate backward_index
, the index going from right to left, for a given forward_index
? Write an expression whose result is assigned to backward_index
! Then, use the indexing operator []
to obtain the two characters, forward
and backward
, from text
.
< your answer >
Q3: Finish unpythonic_palindrome()
below! Add code that adjusts text
such that the function is case insensitive if the ignore_case
argument is True
! Make sure that the function returns once the first pair of corresponding characters does not match!
def unpythonic_palindrome(text, *, ignore_case=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; must be an individual word
ignore_case (bool): If the check is case insensitive; defaults to True
Returns:
is_palindrome (bool)
"""
# answer to Q3
is_palindrome = ...
if ignore_case:
...
# answer to Q1
chars_to_check = ...
for forward_index in range(chars_to_check):
# answer to Q2
backward_index = ...
forward = ...
backward = ...
print(forward, "and", backward) # added for didactical purposes
# answer to Q3
if ...:
is_palindrome = ...
...
return is_palindrome
Q4: Ensure that unpythonic_palindrome()
works for the provided test cases (i.e., the assert
statements do not raise an AssertionError
)! Also, for each of the test cases, provide a brief explanation of what makes them unique!
assert unpythonic_palindrome("noon") is True
< your explanation 1 >
assert unpythonic_palindrome("Hannah") is True
< your explanation 2 >
assert unpythonic_palindrome("Hannah", ignore_case=False) is False
< your explanation 3 >
assert unpythonic_palindrome("radar") is True
< your explanation 4 >
assert unpythonic_palindrome("Hanna") is False
< your explanation 5 >
assert unpythonic_palindrome("Warsaw") is False
< your explanation 6 >
unpythonic_palindrome()
is considered not Pythonic as it uses index variables to implement the looping logic. Instead, we should simply loop over an iterable object to work with its elements one by one.
Q5: Copy your solutions to the previous questions into almost_pythonic_palindrome()
below!
Q6: The reversed() and zip()
built-ins allow us to loop over the same
text
argument in parallel in both forward and backward order. Finish the for
statement's header line to do just that!
Hint: You may need to slice the text
argument.
def almost_pythonic_palindrome(text, *, ignore_case=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; must be an individual word
ignore_case (bool): If the check is case insensitive; defaults to True
Returns:
is_palindrome (bool)
"""
# answers from above
is_palindrome = ...
if ignore_case:
...
chars_to_check = ...
# answer to Q6
for ... in ...:
print(forward, "and", backward) # added for didactical purposes
# answers from above
if ...:
is_palindrome = ...
...
return is_palindrome
Q7: Verify that the test cases work as before!
assert almost_pythonic_palindrome("noon") is True
assert almost_pythonic_palindrome("Hannah") is True
assert almost_pythonic_palindrome("Hannah", ignore_case=False) is False
assert almost_pythonic_palindrome("radar") is True
assert almost_pythonic_palindrome("Hanna") is False
assert almost_pythonic_palindrome("Warsaw") is False
Q8: almost_pythonic_palindrome()
above may be made more Pythonic by removing the variable is_palindrome
with the early exit pattern. Make the corresponding changes!
def pythonic_palindrome(text, *, ignore_case=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; must be an individual word
ignore_case (bool): If the check is case insensitive; defaults to True
Returns:
is_palindrome (bool)
"""
# answers from above
if ignore_case:
...
chars_to_check = ...
for ... in ...:
print(forward, "and", backward) # added for didactical purposes
if ...:
# answer to Q8
...
# answer to Q8
return ...
Q9: Verify that the test cases still work!
assert pythonic_palindrome("noon") is True
assert pythonic_palindrome("Hannah") is True
assert pythonic_palindrome("Hannah", ignore_case=False) is False
assert pythonic_palindrome("radar") is True
assert pythonic_palindrome("Hanna") is False
assert pythonic_palindrome("Warsaw") is False
Q10: pythonic_palindrome()
is not able to check numeric palindromes. In addition to the string method that implements the case insensitivity and that essentially causes the AttributeError
, what abstract behaviors are numeric data types, such as the int
type in the example below, missing that would also cause runtime errors?
pythonic_palindrome(12321)
< your answer >
Q11: Copy your code from pythonic_palindrome()
above into palindrome_ducks()
below and make the latter conform to duck typing!
Hints: You may want to use the str() built-in. You only need to add one short line of code.
def palindrome_ducks(text, *, ignore_case=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; must be an individual word
ignore_case (bool): If the check is case insensitive; defaults to True
Returns:
is_palindrome (bool)
"""
# answer to Q11
...
# answers from above
if ignore_case:
text = ...
chars_to_check = ...
for ... in ...:
if ...:
...
return ...
Q12: Verify that the two new test cases work as well!
assert palindrome_ducks(12321) is True
assert palindrome_ducks(12345) is False
palindrome_ducks()
can not process palindromes that consist of more than one word.
palindrome_ducks("Never odd or even.")
palindrome_ducks("Eva, can I stab bats in a cave?")
palindrome_ducks("A man, a plan, a canal - Panama.")
palindrome_ducks("A Santa lived as a devil at NASA!")
palindrome_ducks("""
Dennis, Nell, Edna, Leon, Nedra, Anita, Rolf, Nora, Alice, Carol, Leo, Jane,
Reed, Dena, Dale, Basil, Rae, Penny, Lana, Dave, Denny, Lena, Ida, Bernadette,
Ben, Ray, Lila, Nina, Jo, Ira, Mara, Sara, Mario, Jan, Ina, Lily, Arne, Bette,
Dan, Reba, Diane, Lynn, Ed, Eva, Dana, Lynne, Pearl, Isabel, Ada, Ned, Dee,
Rena, Joel, Lora, Cecil, Aaron, Flora, Tina, Arden, Noel, and Ellen sinned.
""")
Q13: Implement the final iterative version is_a_palindrome()
below. Copy your solution from palindrome_ducks()
above and add code that removes the "special" characters (and symbols) from the longer example palindromes above so that they are effectively ignored! Note that this processing should only be done if the ignore_symbols
argument is set to True
.
Hints: Use the replace() method on the
str
type to achieve that. You may want to do so within another for
-loop.
def is_a_palindrome(text, *, ignore_case=True, ignore_symbols=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; may be multiple words
ignore_case (bool): If the check is case insensitive; defaults to True
ignore_symbols (bool): If special characters like "." or "?" and others
are ignored; defaults to True
Returns:
is_palindrome (bool)
"""
# answers from above
...
if ignore_case:
...
# answer to Q13
if ignore_symbols:
for ... in ...:
...
# answers from above
chars_to_check = ...
for ... in ...:
if ...:
...
return ...
Q14: Verify that all test cases below work!
assert is_a_palindrome("noon") is True
assert is_a_palindrome("Hannah") is True
assert is_a_palindrome("Hannah", ignore_case=False) is False
assert is_a_palindrome("radar") is True
assert is_a_palindrome("Hanna") is False
assert is_a_palindrome("Warsaw") is False
assert is_a_palindrome(12321) is True
assert is_a_palindrome(12345) is False
assert is_a_palindrome("Never odd or even.") is True
assert is_a_palindrome("Never odd or even.", ignore_symbols=False) is False
assert is_a_palindrome("Eva, can I stab bats in a cave?") is True
assert is_a_palindrome("A man, a plan, a canal - Panama.") is True
assert is_a_palindrome("A Santa lived as a devil at NASA!") is True
assert is_a_palindrome("""
Dennis, Nell, Edna, Leon, Nedra, Anita, Rolf, Nora, Alice, Carol, Leo, Jane,
Reed, Dena, Dale, Basil, Rae, Penny, Lana, Dave, Denny, Lena, Ida, Bernadette,
Ben, Ray, Lila, Nina, Jo, Ira, Mara, Sara, Mario, Jan, Ina, Lily, Arne, Bette,
Dan, Reba, Diane, Lynn, Ed, Eva, Dana, Lynne, Pearl, Isabel, Ada, Ned, Dee,
Rena, Joel, Lora, Cecil, Aaron, Flora, Tina, Arden, Noel, and Ellen sinned.
""") is True
Now, let's look at a recursive formulation in recursive_palindrome()
below.
Q15: Copy the code from is_a_palindrome()
that implements the duck typing, the case insensitivity, and the removal of special characters!
The recursion becomes apparent if we remove the first and the last character from a given text
: text
can only be a palindrome if the two removed characters are the same and the remaining substring is a palindrome itself! So, the word "noon"
has only one recursive call while "radar"
has two.
Further, recursive_palindrome()
has two base cases of which only one is reached for a given text
: First, if recursive_palindrome()
is called with either an empty ""
or a text
argument with len(text) == 1
, and, second, if the two removed characters are not the same.
Q16: Implement the two base cases in recursive_palindrome()
! Use the early exit pattern!
Q17: Add the recursive call to recursive_palindrome()
with a substring of text
! Pass in the ignore_case
and ignore_symbols
arguments as False
! This avoids unnecessary computations in the recursive calls. Why is that the case?
< your answer >
def recursive_palindrome(text, *, ignore_case=True, ignore_symbols=True):
"""Check if a text is a palindrome or not.
Args:
text (str): Text to be checked; may be multiple words
ignore_case (bool): If the check is case insensitive; defaults to True
ignore_symbols (bool): If special characters like "." or "?" and others
are ignored; defaults to True
Returns:
is_palindrome (bool)
"""
# answers from above
...
if ignore_case:
...
if ignore_symbols:
for ... in ...:
...
# answer to Q16
if ...:
...
elif ...:
...
# answer to Q17
return ...
Q18: Lastly, verify that recursive_palindrome()
passes all the test cases below!
assert recursive_palindrome("noon") is True
assert recursive_palindrome("Hannah") is True
assert recursive_palindrome("Hannah", ignore_case=False) is False
assert recursive_palindrome("radar") is True
assert recursive_palindrome("Hanna") is False
assert recursive_palindrome("Warsaw") is False
assert recursive_palindrome(12321) is True
assert recursive_palindrome(12345) is False
assert recursive_palindrome("Never odd or even.") is True
assert recursive_palindrome("Never odd or even.", ignore_symbols=False) is False
assert recursive_palindrome("Eva, can I stab bats in a cave?") is True
assert recursive_palindrome("A man, a plan, a canal - Panama.") is True
assert recursive_palindrome("A Santa lived as a devil at NASA!") is True
assert recursive_palindrome("""
Dennis, Nell, Edna, Leon, Nedra, Anita, Rolf, Nora, Alice, Carol, Leo, Jane,
Reed, Dena, Dale, Basil, Rae, Penny, Lana, Dave, Denny, Lena, Ida, Bernadette,
Ben, Ray, Lila, Nina, Jo, Ira, Mara, Sara, Mario, Jan, Ina, Lily, Arne, Bette,
Dan, Reba, Diane, Lynn, Ed, Eva, Dana, Lynne, Pearl, Isabel, Ada, Ned, Dee,
Rena, Joel, Lora, Cecil, Aaron, Flora, Tina, Arden, Noel, and Ellen sinned.
""") is True