Better approach to check if a sentence is a palindrome in Python - python

I am new to Python (or programming rather) and am trying to learn recursion.
I have written a recursive python function to check if a sentence (not a word) is a palindrome or not.
def checkPalindrome(sentence):
sentence = re.sub('[^\w]', '', sentence.lower())
if len(sentence) == 1:
return True
elif len(sentence) == 2:
return sentence[0] == sentence[1]
else:
return checkPalindrome(sentence[1:-1])
This function works and provides the correct result. Example:
checkPalindrome('Go hang a salami; I’m a lasagna hog')
True
However, since I am removing spaces, punctuations and changing the case of the sentence at the beginning of the function, this step would be computed at every recursive call.
Is there a better way to re-write the function to avoid this?

I think you just answered your own issue: write one function to reduce the input to lower-case letters and call the second one. This second function does the palindrome check and recursion.
However, note that your function doesn't work: the only time it checks the end characters against each other is in the base case of len(sentence) == 2. You need to check just before your recursive call, too:
else:
return sentence[0] == sentence[-1] and
checkPalindrome(sentence[1:-1])
Given this, you could also combine your two base cases:
if len(sentence) <= 1:
return True
Edited per #Joran's suggestion. I've recently been living with too many compiler optimizations; my original is not guaranteed by the Python language definition; this update will work better in the general case.

Related

Single specific character removal without slicing nor strip ,

how can i remove a single character from a string ?
Basically i have a string like :
abccbaa
I wish to remove the first and last letter. With the string.rstrip or string.lstrip methods all of the occurrences are removed and i get a string bccb. the same goes to replace.
is there a way of doing so ? i cannot import anything , i cant use slicing (except accessing single letter ) . Also I cannot use any kind of loops as well .
To get the whole picture , i need to write a recursive palindrome algorithm. My current code is:
def is_palindrome(s):
if s == '':
return True
if s[0] != s[-1]:
return False
else:
s = s.replace(s[0], '')
s = s.replace(s[-1], '')
return is_palindrome(s)
print is_palindrome("abccbaa")
as you can see it will work unless provides with a string like the one in the print line , since more than "edge" letters are stripped .
Slicing/replacing the string isn't needed and is costly because it creates strings over and over. In languages where strings are much less convenient to handle (like C), you wouldn't even have imagined to do like that.
Of course you need some kind of looping, but recursion takes care of that.
You could do it "the old way" just pass the start & end indices recursively, with a nested function to hide the start condition to the caller:
def is_palindrome(s):
def internal_method(s,start,end):
if start>=end:
return True
if s[start] != s[end]:
return False
else:
return internal_method(s,start+1,end-1)
return internal_method(s,0,len(s)-1)
recursion stops if start meets end or if checked letters don't match (with a different outcome of course)
testing a little bit seems to work :)
>>> is_palindrome("")
True
>>> is_palindrome("a")
True
>>> is_palindrome("ab")
False
>>> is_palindrome("aba")
True
>>> is_palindrome("abba")
True
>>> is_palindrome("abbc")
False
I'm taking a guess at what you are looking for here since your question wasn't all that clear but this works for taking off the first and last character of the word you provided?
Python 2.7.14 (default, Nov 12 2018, 12:56:03)
>>> string = "abccbaa"
>>> print(string[1:-1])
bccba

How to repeat some operation without knowing how many times it has to be repeated? [duplicate]

What is an efficient way to check that a string s in Python consists of just one character, say 'A'? Something like all_equal(s, 'A') which would behave like this:
all_equal("AAAAA", "A") = True
all_equal("AAAAAAAAAAA", "A") = True
all_equal("AAAAAfAAAAA", "A") = False
Two seemingly inefficient ways would be to: first convert the string to a list and check each element, or second to use a regular expression. Are there more efficient ways or are these the best one can do in Python? Thanks.
This is by far the fastest, several times faster than even count(), just time it with that excellent mgilson's timing suite:
s == len(s) * s[0]
Here all the checking is done inside the Python C code which just:
allocates len(s) characters;
fills the space with the first character;
compares two strings.
The longer the string is, the greater is time bonus. However, as mgilson writes, it creates a copy of the string, so if your string length is many millions of symbols, it may become a problem.
As we can see from timing results, generally the fastest ways to solve the task do not execute any Python code for each symbol. However, the set() solution also does all the job inside C code of the Python library, but it is still slow, probably because of operating string through Python object interface.
UPD: Concerning the empty string case. What to do with it strongly depends on the task. If the task is "check if all the symbols in a string are the same", s == len(s) * s[0] is a valid answer (no symbols mean an error, and exception is ok). If the task is "check if there is exactly one unique symbol", empty string should give us False, and the answer is s and s == len(s) * s[0], or bool(s) and s == len(s) * s[0] if you prefer receiving boolean values. Finally, if we understand the task as "check if there are no different symbols", the result for empty string is True, and the answer is not s or s == len(s) * s[0].
>>> s = 'AAAAAAAAAAAAAAAAAAA'
>>> s.count(s[0]) == len(s)
True
This doesn't short circuit. A version which does short-circuit would be:
>>> all(x == s[0] for x in s)
True
However, I have a feeling that due the the optimized C implementation, the non-short circuiting version will probably perform better on some strings (depending on size, etc)
Here's a simple timeit script to test some of the other options posted:
import timeit
import re
def test_regex(s,regex=re.compile(r'^(.)\1*$')):
return bool(regex.match(s))
def test_all(s):
return all(x == s[0] for x in s)
def test_count(s):
return s.count(s[0]) == len(s)
def test_set(s):
return len(set(s)) == 1
def test_replace(s):
return not s.replace(s[0],'')
def test_translate(s):
return not s.translate(None,s[0])
def test_strmul(s):
return s == s[0]*len(s)
tests = ('test_all','test_count','test_set','test_replace','test_translate','test_strmul','test_regex')
print "WITH ALL EQUAL"
for test in tests:
print test, timeit.timeit('%s(s)'%test,'from __main__ import %s; s="AAAAAAAAAAAAAAAAA"'%test)
if globals()[test]("AAAAAAAAAAAAAAAAA") != True:
print globals()[test]("AAAAAAAAAAAAAAAAA")
raise AssertionError
print
print "WITH FIRST NON-EQUAL"
for test in tests:
print test, timeit.timeit('%s(s)'%test,'from __main__ import %s; s="FAAAAAAAAAAAAAAAA"'%test)
if globals()[test]("FAAAAAAAAAAAAAAAA") != False:
print globals()[test]("FAAAAAAAAAAAAAAAA")
raise AssertionError
On my machine (OS-X 10.5.8, core2duo, python2.7.3) with these contrived (short) strings, str.count smokes set and all, and beats str.replace by a little, but is edged out by str.translate and strmul is currently in the lead by a good margin:
WITH ALL EQUAL
test_all 5.83863711357
test_count 0.947771072388
test_set 2.01028490067
test_replace 1.24682998657
test_translate 0.941282987595
test_strmul 0.629556179047
test_regex 2.52913498878
WITH FIRST NON-EQUAL
test_all 2.41147494316
test_count 0.942595005035
test_set 2.00480484962
test_replace 0.960338115692
test_translate 0.924381017685
test_strmul 0.622269153595
test_regex 1.36632800102
The timings could be slightly (or even significantly?) different between different systems and with different strings, so that would be worth looking into with an actual string you're planning on passing.
Eventually, if you hit the best case for all enough, and your strings are long enough, you might want to consider that one. It's a better algorithm ... I would avoid the set solution though as I don't see any case where it could possibly beat out the count solution.
If memory could be an issue, you'll need to avoid str.translate, str.replace and strmul as those create a second string, but this isn't usually a concern these days.
You could convert to a set and check there is only one member:
len(set("AAAAAAAA"))
Try using the built-in function all:
all(c == 'A' for c in s)
If you need to check if all the characters in the string are same and is equal to a given character, you need to remove all duplicates and check if the final result equals the single character.
>>> set("AAAAA") == set("A")
True
In case you desire to find if there is any duplicate, just check the length
>>> len(set("AAAAA")) == 1
True
Adding another solution to this problem
>>> not "AAAAAA".translate(None,"A")
True
Interesting answers so far. Here's another:
flag = True
for c in 'AAAAAAAfAAAA':
if not c == 'A':
flag = False
break
The only advantage I can think of to mine is that it doesn't need to traverse the entire string if it finds an inconsistent character.
not len("AAAAAAAAA".replace('A', ''))

Is this a proper recursive call? Function calls itself, but Edx grader doesn't see it

My quite overly complex function (tested and works):
def lenRecur(aStr):
'''
aStr= a string
return length of aStr
'''
lenn=1
n=0
def lenR(aStr,Lenn,n):
if aStr[n+1: ] == '':
return 1
else:
Lenn=Lenn + lenR(aStr[n+1:],Lenn,n)
return Lenn
return lenR(aStr, lenn, n)
In rejecting the code, the edX grader asks me to use a recursive call, even though I believe I used recursion.
Here is their code:
def lenRecur(aStr):
'''
aStr: a string
returns: int, the length of aStr
'''
# Base case: When aStr is the empty string,
# its length is zero.
if aStr == '':
return 0
# Recursive case: If the string is not zero-length, then remove the first
# character and the length is 1 + the length of the rest of the string
return 1 + lenRecur(aStr[1:])
Is the grader just wrong? I would also welcome any comments on which code is better and why (I suspect mine is not).
Thanks
Rather than taking some complex approach to determining "rercursiveness", the grader may simply be looking for lenRecur( to appear twice in your code. Although your inner function lenR() is recursive, the outer function isn't.
Also, your code incorrectly returns
lenRecur("") == 1
In terms of which is better, the fact that the exemplar achieves the correct result in only three lines, with no complex syntax, is a good sign in their favour. Your code does almost exactly the same thing, but in a more roundabout fashion. The fact that you never change n makes it a somewhat pointless variable, just confusing the matter (aStr[n+1:] is always just aStr[1:]).

What possible improvements can be made to a palindrome program?

I am just learning programming in Python for fun. I was writing a palindrome program and I thought of how I can make further improvements to it.
First thing that came to my mind is to prevent the program from having to go through the entire word both ways since we are just checking for a palindrome. Then I realized that the loop can be broken as soon as the first and the last character doesn't match.
I then implemented them in a class so I can just call a word and get back true or false.
This is how the program stands as of now:
class my_str(str):
def is_palindrome(self):
a_string = self.lower()
length = len(self)
for i in range(length/2):
if a_string[i] != a_string[-(i+1)]:
return False
return True
this = my_str(raw_input("Enter a string: "))
print this.is_palindrome()
Are there any other improvements that I can make to make it more efficient?
I think the best way to improvise write a palindrome checking function in Python is as follows:
def is_palindrome(s):
return s == s[::-1]
(Add the lower() call as required.)
What about something way simpler? Why are you creating a class instead of a simple method?
>>> is_palindrome = lambda x: x.lower() == x.lower()[::-1]
>>> is_palindrome("ciao")
False
>>> is_palindrome("otto")
True
While other answers have been given talking about how best to approach the palindrome problem in Python, Let's look at what you are doing.
You are looping through the string by using indices. While this works, it's not very Pythonic. Python for loops are designed to loop over the objects you want, not simply over numbers, as in other languages. This allows you to cut out a layer of indirection and make your code clearer and simpler.
So how can we do this in your case? Well, what you want to do is loop over the characters in one direction, and in the other direction - at the same time. We can do this nicely using the zip() and reversed() builtins. reversed() allows us to get the characters in the reversed direction, while zip() allows us to iterate over two iterators at once.
>>> a_string = "something"
>>> for first, second in zip(a_string, reversed(a_string)):
... print(first, second)
...
s g
o n
m i
e h
t t
h e
i m
n o
g s
This is the better way to loop over the characters in both directions at once. Naturally this isn't the most effective way of solving this problem, but it's a good example of how you should approach things differently in Python.
Building on Lattyware's answer - by using the Python builtins appropriately, you can avoid doing things like a_string[-(i+1)], which takes a second to understand - and, when writing more complex things than palindromes, is prone to off-by-one errors.
The trick is to tell Python what you mean to do, rather than how to achieve it - so, the most obvious way, per another answer, is to do one of the following:
s == s[::-1]
list(s) == list(reversed(s))
s == ''.join(reversed(s))
Or various other similar things. All of them say: "is the string equal to the string backwards?".
If for some reason you really do need the optimisation that you know you've got a palindrome once you're halfway (you usually shouldn't, but maybe you're dealing with extremely long strings), you can still do better than index arithmetic. You can start with:
halfway = len(s) // 2
(where // forces the result to an integer, even in Py3 or if you've done from __future__ import division). This leads to:
s[:halfway] == ''.join(reversed(s[halfway:]))
This will work for all even-length s, but fail for odd length s because the RHS will be one element longer. But, you don't care about the last character in it, since it is the middle character of the string - which doesn't affect its palindromeness. If you zip the two together, it will stop after the end of the short one - and you can compare a character at a time like in your original loop:
for f,b in zip(s[:half], reversed(s[half:])):
if f != b:
return False
return True
And you don't even need ''.join or list or such. But you can still do better - this kind of loop is so idiomatic that Python has a builtin function called all just to do it for you:
all(f == b for f,b in zip(s[:half], reversed(s[half:])))
Says 'all the characters in the front half of the list are the same as the ones in the back half of the list written backwards'.
One improvement I can see would be to use xrange instead of range.
It probably isn't a faster implementation but you could use a recursive test. Since you're learning, this construction is very useful in many situation :
def is_palindrome(word):
if len(word) < 2:
return True
if word[0] != word[-1]:
return False
return is_palindrome(word[1:-1])
Since this is a rather simple (light) function this construction might not be the fastest because of the overhead of calling the function multiple times, but in other cases where the computation are more intensive it can be a very effective construction.
Just my two cents.

Pythonic way of adding "ly" to end of string if it ends in "ing"?

This is my first effort on solving the exercise. I gotta say, I'm kind of liking Python. :D
# D. verbing
# Given a string, if its length is at least 3,
# add 'ing' to its end.
# Unless it already ends in 'ing', in which case
# add 'ly' instead.
# If the string length is less than 3, leave it unchanged.
# Return the resulting string.
def verbing(s):
if len(s) >= 3:
if s[-3:] == "ing":
s += "ly"
else:
s += "ing"
return s
else:
return s
# +++your code here+++
return
What do you think I could improve on here?
def verbing(s):
if len(s) >= 3:
if s.endswith("ing"):
s += "ly"
else:
s += "ing"
return s
How about this little rewrite:
def verbing(s):
if len(s) < 3:
return s
elif s.endswith('ing'):
return s + 'ly'
else:
return s + 'ing'
I would use s.endswith("ing") in the if, which is also a bit faster, because it doesn't create a new string for the comparision.
And second, I would use docstrings for commenting. This way, you can see your description when you do a help(yourmodule) or when you use some autodoc-tool like Sphinx to create a handbook describing your API. Example:
def verbings(s):
"""Given a string, if its length is at least 3, add 'ing' to its end.
Unless it already ends in 'ing', in which case add 'ly' instead.
If the string length is less than 3, leave it unchanged."""
# rest of the function
Third, it's often considered a bad practice to change input parameters. You can do it for dict or list parameters, which can also act as output parameters. But strings are input parameters only (that's why you have the return). The source you have written is valid of course, but is often confusing. Other languages have often a final or const keyword to avoid this confusion, but Python doesn't. So, I would recommend you, to use either a second variable result = s + "ing" and do a return result afterwards, or write return s + "ing".
The rest is perfectly fine. There are of course some constructs in Python which are shorter to write (you will learn them with the time), but they are often not so readable. Therefore I would stay with your solution.
Pretty good for a beginner! Yes, I would say this is the Pythonic way of doing things. I especially like the way you have commented exactly what the function does. Good work there.
Keep working with Python, though. You're doing fine.
def add_string(str1):
if(len(str1)>=3):
if (str1[-3::] == 'ing'):
str1+='ly'
else:
str1+='ing'
return str1
str1="com"
print(add_string(str1))

Categories

Resources