简体   繁体   中英

Fast and pythonic way to find out if a string is a palindrome

[Edit: as someone pointed out I have used improperly the palindrom concept, now I have edited with the correct functions. I have done also some optimizations in the first and third example, in which the for statement goes until it reach half of the string]

I have coded three different versions for a method which checks if a string is a palindrome. The method are implemented as extensions for the class "str"

The methods also convert the string to lowercase, and delete all the punctual and spaces. Which one is the better (faster, pythonic)?

Here are the methods:

1) This one is the first solution that I thought of:

    def palindrom(self):
        lowerself = re.sub("[ ,.;:?!]", "", self.lower())
        n = len(lowerself)
        for i in range(n//2):
            if lowerself[i] != lowerself[n-(i+1)]:
               return False
        return True

I think that this one is the more faster because there aren't transformations or reversing of the string, and the for statement breaks at the first different element, but I don't think it's an elegant and pythonic way to do so

2) In the second version I do a transformation with the solution founded here on stackoverflow (using advanced slicing string[::-1])

# more compact
def pythonicPalindrom(self):
    lowerself = re.sub("[ ,.;:?!]", "", self.lower())
    lowerReversed = lowerself[::-1]
    if lowerself == lowerReversed:
        return True
    else:
        return False

But I think that the slicing and the comparision between the strings make this solution slower.

3) The thirds solution that I thought of, use an iterator:

# with iterator
def iteratorPalindrom(self):
    lowerself = re.sub("[ ,.;:?!]", "", self.lower())
    iteratorReverse = reversed(lowerself)
    for char in lowerself[0:len(lowerself)//2]:
        if next(iteratorReverse) != char:
            return False
    return True

which I think is way more elegant of the first solution, and more efficient of the second solution

So, I decided to just timeit , and find which one was the fastest. Note that the final function is a cleaner version of your own pythonicPalindrome . It is defined as follows:

def palindrome(s, o):
    return re.sub("[ ,.;:?!]", "", s.lower()) == re.sub("[ ,.;:?!]", "", o.lower())[::-1]

Methodology

I ran 10 distinct tests per function. In each test run, the function was called 10000 times, with arguments self="aabccccccbaa", other="aabccccccbaa" . The results can be found below.

            palindrom       iteratorPalindrome      pythonicPalindrome      palindrome  
1           0.131656638            0.108762937             0.071676536      0.072031984
2           0.140950052            0.109713793             0.073781851      0.071860462
3           0.126966087            0.109586756             0.072349792      0.073776719
4           0.125113136            0.108729573             0.094633969      0.071474645
5           0.130878159            0.108602964             0.075770395      0.072455015
6           0.133569472            0.110276694             0.072811747      0.071764222
7           0.128642812            0.111065438             0.072170571      0.072285204
8           0.124896702            0.110218949             0.071898959      0.071841214
9           0.123841905            0.109278358             0.077430437      0.071747112
10          0.124083576            0.108184210             0.080211147      0.077391086

AVG         0.129059854            0.109441967             0.076273540      0.072662766
STDDEV      0.005387429            0.000901370             0.007030835      0.001781309

It would appear that the cleaner version of your pythonicPalindrome is marginally faster, but both functions clearly outclass the alternatives.

It seems that you want to know the execution time of your blocks of code and compare them.

You can use the timeit module.

Here's a quick way:

import timeit

start = timeit.default_timer()

#Your code here

stop = timeit.default_timer()

print stop - start 

Read more:

Option 1

Option 2

You could also time this one-liner that does not use re, but itertools instead:

def isPalindrom(self):
    return all(i==j for i, j in itertools.zip_longest((i.lower() for i in self if i not in " ,.;:?!"), (j.lower() for j in self[::-1] if j not in " ,.;:?!")))

Or, explained in more details:

def isPalindrom(self):
    #using generators to not use memory
    stripped_self = (i.lower() for i in self if i not in " ,.;:?!")
    reversed_stripped_self = (j.lower() for j in self[::-1] if j not in " ,.;:?!")
    return all(self_char==reversed_char for self_char, reversed_char in itertools.zip_longest(stripped_self, reversed_stripped_self))

Recall that filter works on strings:

>>> st="One string, with punc. That also needs lowercase!"
>>> filter(lambda c: c not in " ,.;:?!", st.lower())
'onestringwithpuncthatalsoneedslowercase'

So your test can be a one liner that is obvious in function:

>>> str
'!esacrewol sdeen osla tahT .cnup htiw ,gnirts enO'
>>> filter(lambda c: c not in " ,.;:?!", st.lower())==filter(lambda c: c not in " ,.;:?!", str.lower()[::-1])
True

Or, if you are going to use a regex, just reverse the result with the idiomatic str[::-1] :

>>> "123"[::-1]
'321'
>>> re.sub(r'[ ,.;:?!]', '', st.lower())==re.sub(r'[ ,.;:?!]', '', str.lower())[::-1]
True

The fastest may be to use string.tranlate to delete the characters:

>>> import string
>>> string.translate(st, None, " ,.;:?!")
'OnestringwithpuncThatalsoneedslowercase'
>>> string.translate(st, None, " ,.;:?!")==string.translate(str, None, " ,.;:?!")[::-1]
True

When we pass a word it checks if it can be reversed,If it can be reversed it prints "This is a Palindrome". or "This is NOT a Palindrome"

def reverse(word):
    x = ''
    for i in range(len(word)):
        x += word[len(word)-1-i]
        return x

word = input('give me a word:\n')
x = reverse(word)
if x == word:
    print('This is a Palindrome')
else:
    print('This is NOT a Palindrome')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM