Okay, so I'm trying to make a recursive function that returns True if the function is a palindrome, and False otherwise. However, it doesn't go to the very end, and randomly stops.
Code:
def is_palindrome(word):
if len(word) == 1 or len(word) == 0:
return True
else:
lst = len(word) - 1
if word[0] == word[lst]:
print(len(word), " --> ", word)
print(word[0], " # ", word[lst])
is_palindrome(word[0+1:lst])
else:
return False
For the life of me, I can't figure out why. Here's a sample output:
7 --> racecar
r # r
5 --> aceca
a # a
3 --> cec
c # c
^ It stops right here. Why doesn't it continue and return True when length = 1?
You need to return your call to the recursive function:
def is_palindrome(word):
if len(word) == 1 or len(word) == 0:
return True
else:
lst = len(word) - 1
if word[0] == word[lst]:
print(len(word), " --> ", word)
print(word[0], " # ", word[lst])
return is_palindrome(word[0+1:lst]) # change here
else:
return False
The reason you code appears to stop at the final step of recursion is because you never actually return a value in that case. In some programming languages, such as C or maybe Java, such code would not even compile. Python appears to tolerate it, though it results in your current behavior.
your function is too long.
I prefer to use string[::-1]
#
def is_palindrome(word):
return word == word[::-1]
Related
Can someone tell me why after returning a boolean of False my code goes backwards and chooses True also even though the False was the right output I see this happening in the debugger, I only found this happening with this specific example. So the function of my code is to take a pattern from user and see if it matches the word given by the user, the format of a pattern is "?ead" which would match with the word lead, the question mark is just a placeholder for any character so it can be anything but every other character has to be the same.
n = 0
def match(pattern, word):
global n
if n != len(word): # setting a base case for the recurrsion so it stops at the end of the word
if pattern == "" and word == "":
pass
elif pattern == '' or word == '':
pass
elif len(word) != len(pattern):
return False
if word[n:n+1] == pattern[n:n+1] or pattern[n:n+1] == '?': # goes through each character and compares them for each
n = n +1
match(pattern, word)
print("same")
return True # This is where the code goes back to and returns true after already returning false down below in the else statement.
else:
print("not same")
return False
match("?ut","cat")
The pattern my code fails for "?ut" and the word is "cat", the code sees that the first character is fine since "?" is just a placeholder and it also sees that the second term does not match so it should evaluate to false which it does but then immediately exits and evaluates to true and all this needs to be done with recursion and no loops.
You are trying to do recursion here. It can be done like this:
def match(pattern, word):
#Little debug for you to see what is happening
print (pattern, word)
if len(pattern) != len(word):
return False
if not word and not pattern:
# last round with empty strings, all same this far
return True
if pattern[0] != word[0] and pattern[0] != "?":
return False
# reduce both strings and go to next round
return match(pattern[1:], word[1:])
So you reduce both pattern and word and if you find that they don't match return false. If you get to the end, return True.
You do not return the result of match but return True. You should simplify your cases.
if word[n:n+1] == pattern[n:n+1] or pattern[n:n+1] == '?':
n = n +1
match(pattern, word) # this can return True OR False
print("same")
return True # You always return True
# return match(pattern, word) instead
There is no need for a global "counter" - if you recurse, shorten your pattern/word until nothing is left:
def match(pattern, word):
if len(pattern) != len(word):
return False
# done all I can do
if pattern == "" and word == "":
return True
# no comparison if pattern is ?
if pattern[0] == "?":
# recurse with 1 shorter pattern/word
return match(pattern[1:], word[1:])
# same letters
elif pattern[0] == word[0]:
# recurse with 1 shorter pattern/word
return match(pattern[1:], word[1:])
# different
return False
for pat,wrd in [("?ut","cat"), ("ok","ok"), ("?k","ok"),
("o?","ok"), ("",""), ("tooShort","veryLongWord")]:
print(pat,wrd, "=>", "same" if match(pat,wrd) else "different")
Output:
?ut cat => different
ok ok => same
?k ok => same
o? ok => same
=> same
tooShort veryLongWord => different
Or use the "short" version that does not recurse:
def match(pattern, word):
return len(pattern)==len(word) and all(
(p==w or p == "?" for p,w in zip(pattern, word)))
You should return the result you get back from recursion. Currently you ignore it.
There are some other issues:
Don't use global variables. If you make another call to match, then n will still be the value it had after the first call, and so you'll get an unreliable result from the second call. Instead make it an argument to your function. You can give it a default value.
The condition if pattern == "" and word == "": can never be true, since you already had assured that n != len(word), and as n starts with 0, that if condition would have been false if word was the empty string.
When you pass in the first if blocks, the other code below is still executed, and so you'd make an invalid index reference in the string.
Adding another condition where you delimit strings with single quotes instead of double quotes is not changing anything. You still compare the same strings.
It is overkill to compare slices of one character. Just retrieve the single character you want to compare. So word[n] instead of word[n:n+1]
Don't print the result inside the function. Print at the caller's side.
Correction:
def match(pattern, word, n=0):
if len(word) != len(pattern):
return False
if n >= len(word):
return True
if word[n] != pattern[n] and pattern[n] != '?':
return False
return match(pattern, word, n + 1)
print(match("?ut","cat"))
Not your question, but this kind of recursion is a case of tail recursion, and so it could be written with simple iteration:
def match(pattern, word):
if len(word) != len(pattern):
return False
for i, pat in enumerate(pattern):
if word[i] != pat and pat != '?':
return False
return True
When you are confortable with comprehension syntax you can write that also like this:
def match(pattern, word):
return len(word) == len(pattern) and all(
pat in ('?', ch) for ch, pat in zip(word, pattern)
)
I am trying to write a function named has_no_e that takes a string as an argument and returns False if the string contains the letter e and True if the string does not contains the letter e.
I have written the function using the for loop but I am failing to write a similar function that does the same thing using a while loop.
Here is my function with the for loop:
def has_no_e(word):
for letters in word:
if letters == "e":
return False
return True
And the function I have written is:
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] != "e":
index += 1
return False
return True
Can you tell me what is wrong with my function with the while loop? It returns False every time not depending on whether there is "e" or not.
There are two ways to implement the same fix for this. The one that keeps more closely to your existing code is
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] != "e":
index += 1
continue # skip the rest of the loop and go back to the top
return False
return True
The better one is
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] == "e":
return False
index += 1
return True
Note that the following two pieces of code are roughly equivalent:
for elem in iterable:
# code
_index = 0
while _index < len(iterable):
elem = iterable[_index]
# code
_index += 1
It's more complicated than that, because for loops often use iterators instead of indices, but you get the idea.
Try:
index = 0
while index < len(word):
if word[index] != "e":
index += 1
else:
return False
return True
Currently your code returns false at the end of the first loop
def has_no_e1(_string):
return 'e' not in _string
def has_no_e2(_string):
return _string.find('e') == -1
def has_no_e3(_string):
while _string:
if _string[0] == 'e':
return False
_string = _string[1:]
return True
if __name__ == "__main__":
word = 'With the letter `e`'
print(
has_no_e1(word),
has_no_e2(word),
has_no_e3(word),
)
word = 'Without it'
print(
has_no_e1(word),
has_no_e2(word),
has_no_e3(word),
)
You can use the in operator:
def has_no_e(word):
return 'e' not in word
In case you really need a while loop:
def while_has_no_e(word):
characters = list(word)
while characters:
if characters.pop() == 'e':
return False
return True
print(has_no_e('foo'))
print(has_no_e('element'))
print(while_has_no_e('foo'))
print(while_has_no_e('element'))
Out:
True
False
True
False
Some changes and you are good to go, you just need to write whatever condition you wrote in for loop into while, that's it:
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] == "e":
return False
index+=1
return True
Now let me give you a one liner:
>>> string = 'hello'
>>> False if string.count('e') > 0 else True
False
#changing the contents
>>> string = 'hlo'
>>> False if string.count('e') > 0 else True
True
You don't need to create a function, it exists already and it's called find()
has_e = word.find("e")
The function either returns the exact position of the letter or it returns -1 if it doesn't exist.
Reference
So I have to build a function that checks if something is a palindrome, but it has to be recursive and it has to be linear and it has to ignore punctuations. I can't use any imports and my teacher told me that using the translation function isn't efficient enough. I'm trying to re-engineer it but iI'm very much stumped.
def isPalindrome(word):
newWrd = word.translate(None, "., ").lower()
if len(newWrd) <= 1:
return True
elif newWrd[0] != newWrd [-1]:
return False
else:
return isPalindrome(newWrd[1:-1])
The concept and motivation are simple:
Check the first and last character of the current string.
If they match, repeat the previous operation, but only take the slice of the string that you haven't processed yet.
If they don't match, return False.
Strings of length 1 are palindromic by default.
Here's what I would come up with. The below assumes a unified case and that all punctuation is stripped out of the word, but if you wish to do that in this method, I leave that as an exercise for the reader.
def palindrome(word):
if not word:
return False
if len(word) == 1:
return True
else:
if word[0] == word[-1]:
return palindrome(word[1:-1])
else:
return False
Normally, you should import string and use string.punctuation, but since you don't want to import anything, you can just hard-copy it to your code.
Since you already have the isPalindrome() recursive function, you can write a wrapper function, isPalindromeWrapper(), which will take the input, process it (remove the punctuations) and then call the isPalindrome() function.
punctuations = '!"#$%&\'()*+,-./:;<=>?#[\\]^_`{|}~'
def removePunctuations(word):
return ''.join( [ c for c in word if c not in punctuations ] )
def isPalindrome(word):
print('isPalindrome({}) called.'.format(word))
if len(word) < 2:
return True
if word[0] == word[-1]:
return isPalindrome(word[1:-1])
return False
def isPalindromeWrapper(word):
word = removePunctuations(word)
return isPalindrome(word)
print isPalindromeWrapper('ab,c!b?a')
Output:
isPalindrome(abcba) called.
isPalindrome(bcb) called.
isPalindrome(c) called.
True
So I was doing exercise 6 from chapter 7 from Think Python and it stroke me as odd that this piece of code I wrote as a solution didn't work:
def is_palindrome(word):
if len(word) <= 1:
return True
elif first(word) == last(word):
is_palindrome(middle(word))
else:
return False
It doesn't enter any recursion and I don't know why. It returns None for words longer than 1 character of length. Why is this? When the length of the word reaches 1 or less it should return True!
Why doesn't it work?
PS: Here are first, last and middle deffinitions:
def first(word):
return word[0]
def last(word):
return word[-1]
def middle(word):
return word[1:-1]
You're missing a return:
def is_palindrome(word):
if len(word) <= 1:
return True
elif first(word) == last(word):
return is_palindrome(middle(word)) # <--
else:
return False
So your current snippet is returning None once the elif block is entered, since you don't have an explicit return statement there. In other words, you do compute is_palindrome(middle(word)), but you do nothing with the result.
Maybe working through a simple example would help. Consider calling the original function with an argument of 'aba':
function called
word is now 'aba'
len(word) <= 1 is False, if-body not entered.
first(word) == last(word) is True, elif-body entered:
function called recursively:
word is now 'b'
len(word) <= 1 is True, if-body entered:
True returned
Return value of recursive call discarded (since we have no return in elif)
None returned
Add return
return is_palindrome(middle(word))
I think you are missing to return the method in 5th line of your function.
return is_palindrome(middle(word))
should be there instead of
is_palindrome(middle(word))
I'd like to know how do I check if an input is a palindrome with a while loop, using Python.
Thanks:
i tried this
i = 0
n = len(msg_list)
while i < n:
palindrome = msg_list[i]
if palindrome == msg_list[-1]:
print("Palindrome? True")
msg_list.pop(-1)
else:
print("Palindrome? False")
i=i+1
but at the end I receive an error message that the list index is out of range
You don't need to iterate till the end, but only till the middle character. And compare every character to the character at the same index when counted in reverse:
s = "abcca"
length = len(s)
i = 0
while i < length / 2 + 1:
if s[i] != s[-i - 1]:
print "Not Palindrome"
break
i += 1
else:
print "Palidrome"
else part of the while loop is executed, when the loop completes its iteration without any break.
Alternatively, if you can use anything else than a while loop, then this task is just of single line in Python:
if s == s[::-1]:
print "Palindrome"
Oh, it became two lines.
With a while loop
import string
palin = 'a man, a plan, a canal, panama'
def testPalindrome(in_val):
in_val = in_val.lower()
left, right = 0, len(in_val) - 1
while left < right:
char_left, char_right = '#', '#'
while char_left not in string.lowercase:
char_left = in_val[left]
left += 1
while char_right not in string.lowercase:
char_right = in_val[right]
right -= 1
if char_left != char_right:
return False
return True
print testPalindrome(palin)
Without
>>> palindrome = 'a man, a plan, a canal, panama'
>>> palindrome = palindrome.replace(',', '').replace(' ', '')
>>> palindrome
'amanaplanacanalpanama'
>>> d[::-1] == d
True
A short solution using reversed:
for c, cr in s, reversed(s):
if c != cr:
print("Palindrome? False")
break
else:
print("Palindrome? True")
Another way using a while loop. Once two characters don't match, the while loop stops, so it's quite efficient but of course not the best way to do it in Python.
def palindrome(word):
chars_fw = list(word)
chars_bw = list(reversed(word))
chars_num = len(word)
is_palindrome = True
while chars_num:
if chars_fw[chars_num-1] != chars_bw[chars_num-1]:
is_palindrome = False
break
chars_num -= 1
return is_palindrome
Figured I would add another alternative for people still viewing this question. It uses a while loop, and is reasonably concise (although, I still prefer the if word = word[::-1] approach.
def is_palindrome(word):
word = list(word.replace(' ', '')) # remove spaces and convert to list
# Check input
if len(word) == 1:
return True
elif len(word) == 0:
return False
# is it a palindrome....
while word[0] == word[-1]:
word.pop(0)
word.pop(-1)
if len(word) <= 1:
return True
return False
word = "quiniuq"
pairs = zip(word,reversed(word))
a,b = next(pairs)
try:
while a == b:
a,b = next(pairs)
return False # we got here before exhausting pairs
except StopIteration:
return True # a == b was true for every pair
The use of a while loop here is contrived, but it will consume the whole list and perform the test.
If a while loop wasn't a requirement, one would do: all(a == b for a,b in zip(word,reversed(word)))