回文判断

回文(palindrome)是从左到右和从右到左读起来都一样的字符串。比如说,racecarLive on time, emit no evil 都是回文。请注意,我们仅仅考虑字母和数字,且不分大小写。在得到输入字符串后,请判断它是否是回文。

# 范例行为
>>> is_palindrome("Step on no pets!")
True
>>> is_palindrome("'Tis not a palindrome")
False
>>> is_palindrome("Hi, I am Mai Ih")
True

提示

str.isalnum返回某字符串是否纯粹由数字和字母字符组成(对单个字符的字符串也可以用)。

>>> "I love Python".isalnum()
False
>>> "IlovePython".isalnum()
True

尝试将其与 str.lower 一起使用来过滤所有非数字字母符号并将所有字符小写化,然后再进行回文判断。

本题最简单的解如下。我们使用了 str.join 函数以及步距为负的切片:

def is_palindrome(input_str):
    """ 判断某字符串是否为回文。忽略空格,
        字符大小写,以及非字母数字的字符

        Parameters
        ----------
        s: str
            输入字符串

        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()) 使用了有过滤条件的生成器理解。因此,

"".join(c.lower() for c in input_str if c.isalnum())

等值于以下的代码块:

filtered_str = ""
for char in input_str:
    if char.isalnum():
        filtered_str += char.lower()

生成器表达式不仅更加简短易读,且其对 str.join 的使用使得创建新列表更加高效。在以上代码块中每次调用 filtered_str += c.lower() 都会在内存中创建一个新字符串,而 str.join 在它消耗可迭代物时仅仅使用单个字符串。

接下来,请回忆seq[::-1] 使用的步距-1的切片将会返回序列的反向版本。因此,filtered_str == filtered_str[::-1] 允许我们对比 filtered_str 中的第一个字符和原本字符串的倒数第一个字符,如此继续。因此,这等值于:

is_equal = True
for i in range(len(filtered_str)//2): # 请回忆:5//2 -> 2, 6//2 -> 3
    if filtered_str[i] != filtered_str[-(i+1)]:
        is_equal = False
        break

使用切片来进行这个对比的唯一坏处在于它需要复制一份 filtered_str,而显性的for循环不需要。

在这里指出的效率考量仅仅在 is_palindrome 可能是我们整体代码的效率瓶颈时才值得进行。虽然我们希望读者能够发展编写高效Python代码的直觉,但我们不推荐读者为了过早地优化代码来将代码改得太过难懂。