Python: Recursive Function to check if string is a palindrome - python

So I'm trying to create a recursive function to check if a word is a palindrome. It should ignore all non-alphabetical characters. Here is what I have so far.
def is_palindrome(text):
'''
A Recursive Function that returns True if the parameter, text, is a palindrome, False if not.
Ignores capitalization, punctuation, and spaces.
text: a String
returns True or False
'''
text = list(text)
if len(text) == 0:
return True
else:
if text[0].isalpha():
if text[-1].isalpha():
if text[0].lower() == text[-1].lower():
text.remove(text[0])
text.remove(text[-1])
return is_palindrome(text)
else:
return False
else:
text.remove(text[-1])
return is_palindrome(text)
else:
text.remove(text[0])
return is_palindrome(text)
Here are some test cases...
is_palindrome("abcde")
Results
abcde
False
is_palindrome("aabbaa")
Results
aabbaa
['b', 'b', 'a', 'a']
False
is_palindrome("aa bb cc")
Results
aa bb aa
[' ', 'b', 'b', ' ', 'a', 'a']
['b', 'b', ' ', 'a', 'a']
False
So for some reason, it always directly ends up being false.
Thoughts on how to solve this? All help will be appreciated!

text.remove(text[0]) does not do what you think it does (It removes the first occurrence of that value from the list). To remove by index use slices. You can rewrite this:
text.remove(text[0])
text.remove(text[-1])
to this:
text = text[1:-1]

Is this just for fun, or an exercise? I don't think recursion really makes sense here:
def palindrome(s):
l = len(s)
m = l // 2
return s[:m][::-1] == s[m + (l % 2):]
Then I'd just preprocess the input to remove non alphanumerics and uppers like:
s = 'aBc%$cbA'
palindrome(re.subn('\W', '', s)[0].lower())

From the Python 3 documentation:
list.remove(x)
Remove the first item from the list whose value is x. It is an error if there is no such item.
When you call text.remove(text[-1]) for the list based on the string aabbaa, the first a in the list will be removed, which is not always the desired behavior. If you want to remove the first and last elements of the list, you can use a slice as user1434070 posted. An alternate method is to use list.pop(x) to remove the element with a specified index, which is accomplished by replacing the calls to remove with the following code:
text.pop(0)
if len(text) == 0:
return True
text.pop(-1)
Note that the size of the list must be checked after the first pop in case it contained a single letter. This occurs in the deepest level of recursion when the original palindrome contains an odd number of characters.

list.remove() removes the first occurrence of an element in the list. If that element is present at the beginning and end, it doesn't matter that you selected that element by indexing the list to get the last element - it'll still remove the first one it sees. list.pop() would be more appropriate, as that removes elements based on their index rather than their value.
def is_palindrome(text):
text = list(text)
if len(text) == 0:
return True
else:
if text[0].isalpha():
if text[-1].isalpha():
if text[0].lower() == text[-1].lower():
text.pop(0)
text.pop()
return is_palindrome(text)
else:
return False
else:
text.pop()
return is_palindrome(text)
else:
text.pop(0)
return is_palindrome(text)
However, you can further improve your program by filtering out any ignored characters at the beginning:
def is_palindrome(text):
text = ''.join(filter(str.isalpha, text)).lower()
if not text:
return True
text = list(text)
if text[0].lower() == text[-1].lower():
text.pop(0)
text.pop()
return is_palindrome(text)
return False
A further optimization would be to use a default argument to note whether the string has been cleaned already, and send a slice to the recursive call:
def is_palindrome(text, cleaned=False):
if not cleaned:
text = ''.join(filter(str.isalpha, text)).lower()
if not text:
return True
if text[0] != text[-1]:
return False
return is_palindrome(text[1:-1], 1)

Related

String exercise in Python which detects certain letters

I am trying to create a function in Python which allows me to know if a string contains a letter "y" which appears in the beginning of a word and before a consonant. For example, the sentence "The word yes is correct but the word yntelligent is incorrect" contains the "y" of the word "yncorrect", so the function has to return True. In addition, it has to return true if the "y" is in capital letters and verifies those same conditions.
I have done it in the following way and it appears as if the program works but I was asked to use the method for strings in Python find and I havent't been able to include it. Any hint about how to do it using the method find? Thank you very much.
def function(string):
resultado=False
consonants1="bcdfghjklmnñpqrstvwxyz"
consonants2="BCDFGHJKLMNÑPQRSTVWXYZ"
for i in range(0,len(string)):
if string[i]=="y" and string[i-1]==" " and string[i+1] in consonants1:
resultado=True
break
if string[i]=="Y" and string[i-1]==" " and string[i+1] in consonants2:
resultado=True
break
return resultado
print(function("The word yes is correct but the word yntelligent is incorrect"))
Basically it is better to use re
consonants1="BCDFGHJKLMNÑPQRSTVWXYZ"
for i in consonants1:
if (a:= string.upper().find(f' Y{i}')) != -1:
print(...)
break
I think the function you want isn't find, but finditer from the package 're' (find will only give you the first instance of y, while finditer will return all instances of y)
import re
import string
consonants = string.ascii_lowercase
vowels = ['a', 'e', 'i', 'o', 'u']
for vowel in vowels:
consonants.remove(vowel)
def func(string):
for x in re.finditer('y', string.lower()):
if string[x.start() + 1] in consonants:
return True
return False
The function find returns the index at which the string first begins or is found. So, it returns the first index, else -1. This won't work for your use cases, unless you make it a bit more complicated.
Method One: Check every combination with find.
You have to two results, one to check if its the first word, or if its in any other word. Then return True if they hit. Otherwise return false
def function(string):
consonants1="bcdfghjklmnñpqrstvwxyz"
string = string.lower()
for c in consonants1:
result1 = string.find(" y" + c)
result2 = string.find("y" + c)
if result1 != 1 or result2 == 0:
return True
return False
Method Two: loop through find results.
You can use .find but it will be counter-intuitive. You can use .find and loop through each new substring excluding the past "y/Y", and do a check each time you find one. I would also convert the string to .lower() (convert to lowercase) so that you don't have to worry about case sensitivity.
def function(string):
consonants1="bcdfghjklmnñpqrstvwxyz"
string = string.lower()
start_index = 0
while start_index < len(string):
temp_string = string[start_index+1:end] ## add the 1 so that you don't include the past y
found_index = temp_string.find("y")
if found_index == -1: return False
og_index = start_index + found_index
## check to see if theres a " yConsonants1" combo or its the first word without space
if (string[og_index - 1] == " " and string[og_index+1] in consonants1) or (string[og_index+1] in consonants1 and og_index == 0):
return True
else:
start_index = og_index
return False
Here's how I would go about solving it:
Look up what the find function does. I found this resource online which says that find will return the index of the first occurrence of value (what's passed into the function. If one doesn't exist, it returns -1.
Since we're looking for combinations of y and any consonant, I'd just change the arrays of your consonants to be a list of all the combinations that I'm looking for:
# Note that each one of the strings has a space in the beginning so that
# it always appears in the start of the word
incorrect_strings = [" yb", " yc", ...]
But this won't quite work because it doesn't take into account all the permutations of lowercase and uppercase letters. However, there is a handy trick for handling lowercase vs. uppercase (making the entire string lowercase).
string = string.lower()
Now we just have to see if any of the incorrect strings appear in the string:
string = string.lower()
incorrect_strings = [" yb", " yc", ...]
for incorrect_string in incorrect_strings:
if string.find(incorrect_string) >= 0:
# We can early return here since it contains at least one incorrect string
return True
return False
To be honest, since you're only returning a True/False value, I'm not too sure why you need to use the find function. Doing if incorrect_string in string: would work better in this case.
EDIT
#Barmar mentioned that this wouldn't correctly check for the first word in the string. One way to get around this is to remove the " " from all the incorrect_strings. And then have the if case check for both incorrrect_string and f" {incorrect_string}"
string = string.lower()
incorrect_strings = ["yb", "yc", ...]
for incorrect_string in incorrect_strings:
if string.find(incorrect_string) >= 0 or string.find(f" {incorrect_string}"):
# We can early return here since it contains at least one incorrect string
return True
return False

How to check if the substring is present in the parent string in same order

Input
Indian
3
nda
dan
ndani
Output
True
True
False
Explanation
1st line is the parent string
2nd line is the number of test case
The next n lines are the queries
First and Second query substring are in same order as in parent string.
for each query, initialize a pointer at the beginning of the query string, increment it only when you match an alphabet from the parent string while looping through the parent string
start = 0
for x in parent:
if x == query[start]:
start += 1
if start == len(query):
print(True)
break
else:
print(False)
You can do this for each query.
You can do this by creating a regular expression from the substring you are trying to match. For example, for the first test case if you want to know if 'nda' can be found in 'Indian', then form the regular expression n.*d.*a and do a search for that expression in 'Indian':
import re
string = 'Indian'
substrings = [
'nda',
'dan',
'ndan1'
]
for substring in substrings:
rex = '.*'.join(re.escape(ch) for ch in substring) # 'n.*d.*a'
print('True' if re.search(rex, string) else 'False')
Prints:
True
True
False
You just need to check each char against the main string index from i to end.
You can try this:
main_str = 'Indian'
words = ['nda','dan','ndani']
for word in words:
checker = []
for i in range(len(main_str)):
if i == len(word):
break
if word[i] in main_str[i::]:
checker.append('True')
else:
checker.append('False')
if 'False' in checker:
print('False')
else:
print('True')
It's not very efficient and intuitive but it gets the jobe done (I think). You can just modify the code to fit your input
This is a very simple program. But it will work -
string = 'Indian'
words = ['3','nda','dan','ndani']
for sub in words:
if sub in the string:
return True
else:
return False
This is my approach - use iter.
def isSubsequence(sub: str, orig: str) -> bool:
it = iter(orig) #
return all(ch in orig for ch in sub)
if __name__ == '__main__':
sub = 'rice'
orig = 'practice'
assert isSubsequence(sub, orig) == True
assert isSubsequence('pace', orig) == True
assert isSubsequence('acts', orig) == False

Recursion and list comprehension?

is "lst[1:-1]" any different from "lst[1:]" ? in this situation:
def is_pal(string):
if len(string) < 2:
return True
elif string[0] != string[-1]:
return False
else:
return is_pal(string[1:-1])
The code is Checking whether string is palindrome.
-1 is exclusive, meaning [1:-1] will return the whole string except for the first (0) and the last characters.
BTW you can just reverse the text and check it that way:
def is_pal(string):
return string[::-1] == string
consider a string,
s = "abcdef"
s[1:] = "bcdef"
s[1:-1] = "bcde"
To check whether the given string is palindrome or not,
just compare the given string s with string s[::-1].

Super Reduced String python

I've been having problems with this simple hackerrank question. My code works in the compiler but hackerrank test is failing 6 test cases. One of which my output is correct for (I didn't pay premium). Is there something wrong here?
Prompt:
Steve has a string of lowercase characters in range ascii[‘a’..’z’]. He wants to reduce the string to its shortest length by doing a series of operations in which he selects a pair of adjacent lowercase letters that match, and then he deletes them. For instance, the string aab could be shortened to b in one operation.
Steve’s task is to delete as many characters as possible using this method and print the resulting string. If the final string is empty, print Empty String
Ex.
aaabccddd → abccddd → abddd → abd
baab → bb → Empty String
Here is my code:
def super_reduced_string(s):
count_dict = {}
for i in s:
if (i in count_dict.keys()):
count_dict[i] += 1
else:
count_dict[i] = 1
new_string = ''
for char in count_dict.keys():
if (count_dict[char] % 2 == 1):
new_string += char
if (new_string is ''):
return 'Empty String'
else:
return new_string
Here is an example of output for which it does not work.
print(super_reduced_string('abab'))
It outputs 'Empty String' but should output 'abab'.
By using a counter, your program loses track of the order in which it saw characters. By example with input 'abab', you program sees two a's and two b's and deletes them even though they are not adjacent. It then outputs 'Empty String' but should output 'abab'.
O(n) stack-based solution
This problem is equivalent to finding unmatched parentheses, but where an opening character is its own closing character.
What this means is that it can be solved in a single traversal using a stack.
Since Python can return an actual empty string, we are going to output that instead of 'Empty String' which could be ambiguous if given an input such as 'EEEmpty String'.
Code
def super_reduced_string(s):
stack = []
for c in s:
if stack and c == stack[-1]:
stack.pop()
else:
stack.append(c)
return ''.join(stack)
Tests
print(super_reduced_string('aaabab')) # 'abab'
print(super_reduced_string('aabab')) # 'bab'
print(super_reduced_string('abab')) # 'abab'
print(super_reduced_string('aaabccddd ')) # 'abd'
print(super_reduced_string('baab ')) # ''
I solved it with recursion:
def superReducedString(s):
if not s:
return "Empty String"
for i in range(0,len(s)):
if i < len(s)-1:
if s[i] == s[i+1]:
return superReducedString(s[:i]+s[i+2:])
return s
This code loops over the string and checks if the current and next letter/position in the string are the same. If so, these two letters/positions I get sliced from the string and the newly created reduced string gets passed to the function.
This occurs until there are no pairs in the string.
TESTS:
print(super_reduced_string('aaabccddd')) # 'abd'
print(super_reduced_string('aa')) # 'Empty String'
print(super_reduced_string('baab')) # 'Empty String'
I solved it by creating a list and then add only unique letters and remove the last letter that found on the main string. Finally all the tests passed!
def superReducedString(self, s):
stack = []
for i in range(len(s)):
if len(stack) == 0 or s[i] != stack[-1]:
stack.append(s[i])
else:
stack.pop()
return 'Empty String' if len(stack) == 0 else ''.join(stack)
I used a while loop to keep cutting down the string until there's no change:
def superReducedString(s):
repeat = set()
dups = set()
for char in s:
if char in repeat:
dups.add(char + char)
else:
repeat.add(char)
s_old = ''
while s_old != s:
s_old = s
for char in dups:
if char in s:
s = s.replace(char, '')
if len(s) == 0:
return 'Empty String'
else:
return s

Palindrome recursive function

I tried to write a recursive function that says if a string is a palindrome, but all I get is an infinite loop and I don't know what the problem is
def isPalindrome(S):
listush=list(S) #listush=['a', 'b', 'n', 'n', 'b', 'a']
length=len(listush) #length=6
if length==0 or length==1:
return S, "is a palindrome!"
elif listush[0]!=listush[-1]:
return S, "is not a palindrome!"
else:
del listush[0]
del listush[-1]
return isPalindrome(S)
print isPalindrome("abnnba")
Hope this helps
def ispalindrome(word):
if len(word)<=1:
print("Palindrome")
return
else:
if word[0]!=word[-1]:
print("Not a palindrome")
return
return ispalindrome(word[1:len(word)-1])
word=input("Enter word ")
ispalindrome(word)
First of all, indent your code properly.
Secondly, you are calling the function again with the same argument. Call with 'listush' list from which you are deleting or delete from 'S' and recurse with S argument.
There's no need for creating a list. A python string is already an indexable sequence.
Even better, we can employ slicing and let the function return True and False instead of a tuple with text, With all of this, isPalindrome() becomes a one-liner:
def isPalindrome(S):
return len(S) < 2 or (S[0] == S[-1] and isPalindrome(S[1:-2]))
print isPalindrome('A')
>>> True
print isPalindrome('AA')
>>> True
print isPalindrome('BAAB')
>>> True
print isPalindrome('ABAB')
>>> False
There are some things I would like to say about your code
You can send a slice of the list, saving you the trouble of deleting
elements.
You don't need to convert it to a list, all the operations you need
in finding palindrome are supported by strings.
You are returning S in the recursive function, which would be an
empty list(or string) because it is diminishing each recursion. In
recursive cases, I suggest you to just return True or False
Here is an example.
def isPalindrome(S):
length=len(S)
if length < 2:
return True
elif S[0] != S[-1]:
return False
else:
return isPalindrome(S[1:length - 1])
Simple as that.
If you do an print(listush) you can see, that your list never changes.
The following modification of your code works:
def isPalindrome(testStr, orig=None):
if orig is None:
orig = testStr
length = len(testStr) #length=6
print(testStr)
if length == 0 or length == 1:
return orig, "is a palindrome!"
elif testStr[0] != testStr[-1]:
return orig, "is not a palindrome!"
else:
return isPalindrome(testStr[1:-1], orig)
print isPalindrome("abnnba")

Categories

Resources