A palindrome is a string that reads the same from left to right and from right to left. Strings like
Live on time, emit no evilare palindromes. Notice that only valid alphanumeric characters are accounted for and that palindromes are not case-sensitive. Given a string, return whether or not it is a palindrome.
# example behavior >>> is_palindrome("Step on no pets!") True >>> is_palindrome("'Tis not a palindrome") False >>> is_palindrome("Hi, I am Mai Ih") True
str.isalnum returns whether or not a string has purely alphanumeric characters (it works for single-character strings too).
>>> "I love Python".isalnum() False >>> "IlovePython".isalnum() True
Consider using this along with
str.lower to filter out ignored characters and to normalize all of the character casing in the string before assessing whether or not it is a palindrome.
The simplest solution to this problem is the following, where we make use of the
str.join function as well as slicing with a negative step:
def is_palindrome(input_str): """ Given a string, determine if it is a palindrome. Whitespaces, character-casing, and non-alphanumeric characters are all ignored. Parameters ---------- s: str Input string Returns ------- bool """ filtered_str = "".join(c.lower() for c in input_str if c.isalnum()) return filtered_str == filtered_str[::-1]
(c.lower() for c in input_str if c.isalnum()) has the form of a filtering generator comprehension. Thus,
"".join(c.lower() for c in input_str if c.isalnum())
is equivalent to the long-form code:
filtered_str = "" for char in input_str: if char.isalnum(): filtered_str += char.lower()
The generator comprehension expression is not only more concise and readable, but its use of
str.join also makes it a more efficient means for constructing a new list. Each call to
filtered_str += c.lower() in the long-form code creates a new string in memory, whereas
str.join forms a single string as it consumes the input iterable.
Next, recall that
seq[::-1] slices a sequence with a step of -1, which produces the sequence in reverse order. Thus
filtered_str == filtered_str[::-1] allows us to compare the first character in
filtered_str with the last and so on. This is equivalent to:
is_equal = True for i in range(len(filtered_str)//2): # recall: 5//2 -> 2, 6//2 -> 3 if filtered_str[i] != filtered_str[-(i+1)]: is_equal = False break
The only downside to using slicing to perform this comparison is that it requires that a copy of
filtered_str be created, whereas using the explicit for-loop does not.
We must note that the performance differences pointed out here should only concern us if
is_palindrome is potentially a performance bottleneck for our code. Although we want the reader to develop an intuition for writing efficient Python code, we discourage mangling code for the sake of premature optimization.