Contradictory outputs in simple recursive function - python

Note: Goal of the function is to remove duplicate(repeated) characters.
Now for the same given recursive function, different output pops out for different argument:
def rd(x):
if x[0]==x[-1]:
return x
elif x[0]==x[1]:
return rd(x[1: ])
else:
return x[0]+rd(x[1: ])
print("Enter a sentence")
r=raw_input()
print("simplified: "+rd(r))
This functions works well for the argument only if the duplicate character is within the starting first six characters of the string, for example:
if r=abcdeeeeeeefghijk or if r=abcdeffffffghijk
but if the duplicate character is after the first six character then the output is same as the input,i.e, output=input. That means with the given below value of "r", the function doesn't work:
if r=abcdefggggggggghijkde (repeating characters are after the first six characters)

The reason you function don't work properly is you first if x[0]==x[-1], there you check the first and last character of the substring of the moment, but that leave pass many possibility like affffffa or asdkkkkkk for instance, let see why:
example 1: 'affffffa'
here is obvious right?
example 2: 'asdkkkkkk'
here we go for case 3 of your function, and then again
'a' +rd('sdkkkkkk')
'a'+'s' +rd('dkkkkkk')
'a'+'s'+'d' +rd('kkkkkk')
and when we are in 'kkkkkk' it stop because the first and last are the same
example 3: 'asdfhhhhf'
here is the same as example 2, in the recursion chain we arrive to fhhhhf and here the first and last are the same so it leave untouched
How to fix it?, simple, as other have show already, check for the length of the string first
def rd(x):
if len(x)<2: #if my string is 1 or less character long leave it untouched
return x
elif x[0]==x[1]:
return rd(x[1: ])
else:
return x[0]+rd(x[1: ])
here is alternative and iterative way of doing the same: you can use the unique_justseen recipe from itertools recipes
from itertools import groupby
from operator import itemgetter
def unique_justseen(iterable, key=None):
"List unique elements, preserving order. Remember only the element just seen."
# unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
# unique_justseen('ABBCcAD', str.lower) --> A B C A D
return map(next, map(itemgetter(1), groupby(iterable, key)))
def clean(text):
return "".join(unique_justseen(text)
test
>>> clean("abcdefggggggggghijk")
'abcdefghijk'
>>> clean("abcdefghijkkkkkkkk")
'abcdefghijk'
>>> clean("abcdeffffffghijk")
'abcdefghijk'
>>>
and if you don't want to import anything, here is another way
def clean(text):
result=""
last=""
for c in text:
if c!=last:
last = c
result += c
return result

The only issue I found with you code was the first if statement. I assumed you used it to make sure that the string was at least 2 long. It can be done using string modifier len() in fact the whole function can but we will leave it recursive for OP sake.
def rd(x):
if len(x) < 2: #Modified to return if len < 2. accomplishes same as original code and more
return x
elif x[0]==x[1]:
return rd(x[1: ])
else:
return x[0]+rd(x[1: ])
r=raw_input("Enter a sentence: ")
print("simplified: "+rd(r))
I would however recommend not making the function recursive and instead mutating the original string as follows
from collections import OrderedDict
def rd(string):
#assuming order does matter we will use OrderedDict, no longer recursive
return "".join(OrderedDict.fromkeys(string)) #creates an empty ordered dict eg. ({a:None}), duplicate keys are removed because it is a dict
#grabs a list of all the keys in dict, keeps order because list is orderable
#joins all items in list with '', becomes string
#returns string
r=raw_input("Enter a sentence: ")
print("simplified: "+rd(r))

Your function is correct but, if you want to check the last letter, the function must be:
def rd(x):
if len(x)==1:
return x
elif x[0]==x[1]:
return rd(x[1: ])
else:
return x[0]+rd(x[1: ])
print("Enter a sentence")
r=raw_input()
print("simplified: "+rd(r))

Related

HackerRank Game of Thrones

I am trying to solve this problem on HackerRank and I am having a issue with my logic. I am confused and not able to think what I'm doing wrong, feels like I'm stuck in logic.
Question link: https://www.hackerrank.com/challenges/game-of-thrones/
I created a dictionary of alphabets with value 0. And then counting number of times the alphabet appears in the string. If there are more than 1 alphabet characters occurring 1 times in string, then obviously that string cannot become a palindrome. That's my logic, however it only pass 10/21 test cases.
Here's my code:
def gameOfThrones(s):
alpha_dict = {chr(x): 0 for x in range(97,123)}
counter = 0
for i in s:
if i in alpha_dict:
alpha_dict[i] += 1
for key in alpha_dict.values():
if key == 1:
counter += 1
if counter <= 1:
return 'YES'
else:
return 'NO'
Any idea where I'm going wrong?
Explanation
The issue is that the code doesn't really look for palindromes. Let's step through it with a sample text based on a valid one that they gave: aaabbbbb (the only difference between this and their example is that there is an extra b).
Your first for loop counts how many times the letters appear in the string. In this case, 3 a and 5 b with all the other characters showing up 0 times (quick aside, the end of the range function is exclusive so this would not count any z characters that might show up).
The next for loop counts how many character there are that show up only once in the string. This string is made up of multiple a and b characters, more than the check that you have for if key == 1 so it doesn't trigger it. Since the count is less than 1, it returns YES and exits. However aaabbbbb is not a palindrome unscrambled.
Suggestion
To fix it, I would suggest having more than just one function so you can break down exactly what you need. For example, you can have a function that would return a list of all the unscrambled possibilities.
def allUnscrambled(string)->list:
# find all possible iterations of the string
# if given 'aabb', return 'aabb', 'abab', 'abba', 'bbaa', 'baba', 'baab'
return lstOfStrings
After this, create a palindrome checker. You can use the one shown by Dmitriy or create your own.
def checkIfPalindrome(string)->bool:
# determine if the given string is a palindrome
return isOrNotPalindrome
Put the two together to get a function that will, given a list of strings, determine if at least one of them is a palindrome. If it is, that means the original string is an anagrammed palindrome.
def palindromeInList(lst)->bool:
# given the list of strings from allUnscrambled(str), is any of them a palindrome?
return isPalindromeInList
Your function gameOfThrones(s) can then call this palindromeInList( allUnscrambled(s) ) and then return YES or NO depending on the result. Breaking it up into smaller pieces and delegating tasks is usually a good way to handle these problems.
Corrected the logic in my solution. I was just comparing key == 1 and not with every odd element.
So the corrected code looks like:
for key in alpha_dict.values():
if key % 2 == 1:
counter += 1
It passes all the testcases on HackerRank website.
The property that you have to check on the input string is that the number of characters with odd repetitions must be less than 1. So, the main ingredients to cook you recipe are:
a counter for each character
an hash map to store the counters, having the characters as keys
iterate over the input string
A plain implementation could be:
def gameOfThrones(s):
counters = {}
for c in s:
counters[c] = counters.get(c, 0) + 1
n_odd_characters = sum(v % 2 for v in counters.values())
Using a functional approach, based on reduce from functools:
from functools import reduce
def gamesOfThrones(s):
return ['NO', 'YES'][len(reduce(
lambda x, y: (x | {y: 1}) if y not in x else (x.pop(y) and x),
s,
{}
)) <= 1]
If you want, you can use the Counter class from collections to make your code more concise:
def gamesOfThrones(s):
return ['NO', 'YES'][sum([v % 2 for v in Counter(s).values() ]) <= 1]

How can I simplify my function and make it more pythonic?

I have written a Python function for some post processing in my text recognition algorithm. It works fine, but it seems to be kind of lengthy and has many if-else-conditions. I am looking for a way to simplify my code:
def postProcessing(new_s): #new_s is a list
import string
new_s=removeFrontLetters(new_s) #This function has to be called first
if all(ch in string.digits for ch in new_s[:4]): #checking if first 4 elements are digits.
if all(ch in string.ascii_letters for ch in new_s[4:5]): #checking if the [4] and [5] are letters
if len(new_s)>=7:
if new_s[6]=='C':
new_s=new_s[:7] #if length>=7 and the [6] =='C' the reversed of the first 7 elements has to be returned.
new_s=list(reversed(new_s))
return(new_s)
else:
new_s=new_s[:6] #if length>=7 but the [6] =!'C' the reversed of the first 6 elements has to be returned.
new_s=list(reversed(new_s))
return(new_s)
else:
new_s=list(reversed(new_s)) #if length<7, the reversed of the given list has to be returned.
return(new_s)
else:
print('not valid') #if the [4] and [5] are not letters, it is not valid
else:
print('not valid') #if the [:4] are not digits, it is not valid
This seems very beginner-level and lengthy. I am a beginner, but I am trying to improve my function. Do you have suggestions?
You can invert your if statements and use early returns to reduce the indentation of your code.
def postProcessing(new_s): # new_s is a list
import string
new_s = removeFrontLetters(new_s) # This function has to be called first
if not all(ch in string.digits for ch in new_s[:4]): # checking if first 4 elements are digits.
raise ValueError("First four elements must be digits")
if not all(ch in string.ascii_letters for ch in new_s[4:5]): # checking if the [4] and [5] are letters
raise ValueError("First elements 4 and 5 must be digits")
if len(new_s) <= 7:
new_s = list(reversed(new_s)) # if length<7, the reversed of the given list has to be returned.
return (new_s)
if new_s[6] == 'C':
new_s = new_s[
:7] # if length>=7 and the [6] =='C' the reversed of the first 7 elements has to be returned.
new_s = list(reversed(new_s))
return (new_s)
new_s = new_s[:6] # if length>=7 but the [6] =!'C' the reversed of the first 6 elements has to be returned.
new_s = list(reversed(new_s))
return (new_s)
It's quite neat you ask 'the world' for advise. Did you know there's a dedicated stackexchange site for this? https://codereview.stackexchange.com/.
Unless you insist writing Python code for this, it seems that you need a regular expression here.
So some tips:
use regex for pattern matching
use variables to express what an expression means
use exceptions instead of an 'invalid value' string
separate the 'parsing' from the processing to keep functions small and focused
use doctest to document and test small functions
def post_process(new_s):
"""
reverse a string (with a twist)
what follows is a doctest. You can run it with
$ python -m doctest my_python_file.py
>>> post_process('1234abCdef')
'Cba4321'
>>> post_process('1234abdef')
'ba4321'
"""
cmds = {
'C': cmd_c,
'': cmd_none
}
command, content = command_and_content(new_s)
process = cmds[command]
return process(content)
def cmd_c(content):
return 'C' + "".join(reversed(content))
def cmd_none(content):
return "".join(reversed(content))
The command_and_content function replaces the parsing logic:
def command_and_content(new_s):
# get help on https://regex101.com/ to find the
# regular expression for four digits and two letters
digits_then_ascii = re.compile(r"(\d{4}[a-z]{2})(C?)(.*)")
if match := digits_then_ascii.match(new_s):
content = match.group(1)
command = match.group(2)
return command, content
# pylint will ask you to not use an else clause after a return
# Also, Python advises exceptions for notifying erroneous input
raise ValueError(new_s)
From the context you provided, I assume that all this processing can happen in-place (i.e. without the need to allocate additional memory). The benefit of lists is that they are mutable, so you can actually do all your operations in-place.
This adheres to the style conventions (PEP 8) and uses correct type annotations (PEP 484):
from string import digits, ascii_letters
def remove_front_letters(new_s: list[str]) -> None:
...
raise NotImplementedError
def post_processing(new_s: list[str]) -> None:
remove_front_letters(new_s)
if any(ch not in digits for ch in new_s[:4]):
raise ValueError("The first 4 characters must be digits")
if any(ch not in ascii_letters for ch in new_s[4:6]):
raise ValueError("The 5th and 6th characters must be letters")
if len(new_s) >= 7:
if new_s[6] == 'C':
del new_s[7:]
else:
del new_s[6:]
new_s.reverse()
If you do want a new list, you can just call this function with a .copy() of your input list.
References: list methods; del statement
PS: If you use Python version 3.8 or lower, instead of list[str] you'll need to use typing.List[str].
Also someone mentioned the possibility of replacing the iteration via all() (or any()) with a "".join(...).isdigit() for example. While this is certainly also correct and technically less code, I am not sure it is necessarily more readable. More importantly it creates a new string in the process, which I don't think is necessary.
By the way, you could even reduce that conditional deletion of list elements to a one liner like this:
...
if len(new_s) >= 7:
del new_s[7 if new_s[6] == 'C' else 6:]
new_s.reverse()
But I would argue that this is worse because it is less readable. Personal preference I guess.

How does comparing two chars (within a string) work in Python

I am starting to learn Python and looked at following website: https://www.w3resource.com/python-exercises/string/
I work on #4 which is "Write a Python program to get a string from a given string where all occurrences of its first char have been changed to '$', except the first char itself."
str="restart"
char=str[0]
print(char)
strcpy=str
i=1
for i in range(len(strcpy)):
print(strcpy[i], "\n")
if strcpy[i] is char:
strcpy=strcpy.replace(strcpy[i], '$')
print(strcpy)
I would expect "resta$t" but the actual result is: $esta$t
Thank you for your help!
There are two issues, first, you are not starting iteration where you think you are:
i = 1 # great, i is 1
for i in range(5):
print(i)
0
1
2
3
4
i has been overwritten by the value tracking the loop.
Second, the is does not mean value equivalence. That is reserved for the == operator. Simpler types such as int and str can make it seem like is works in this fashion, but other types do not behave this way:
a, b = 5, 5
a is b
True
a, b = "5", "5"
a is b
True
a==b
True
### This doesn't work
a, b = [], []
a is b
False
a == b
True
As #Kevin pointed out in the comments, 99% of the time, is is not the operator you want.
As far as your code goes, str.replace will replace all instances of the argument supplied with the second arg, unless you give it an optional number of instances to replace. To avoid replacing the first character, grab the first char separately, like val = somestring[0], then replace the rest using a slice, no need for iteration:
somestr = 'restart' # don't use str as a variable name
val = somestr[0] # val is 'r'
# somestr[1:] gives 'estart'
x = somestr[1:].replace(val, '$')
print(val+x)
# resta$t
If you still want to iterate, you can do that over the slice as well:
# collect your letters into a list
letters = []
char = somestr[0]
for letter in somestr[1:]: # No need to track an index here
if letter == char: # don't use is, use == for value comparison
letter = '$' # change letter to a different value if it is equal to char
letters.append(letter)
# Then use join to concatenate back to a string
print(char + ''.join(letters))
# resta$t
There are some need of modification on your code.
Modify your code with as given in below.
strcpy="restart"
i=1
for i in range(len(strcpy)):
strcpy=strcpy.replace(strcpy[0], '$')[:]
print(strcpy)
# $esta$t
Also, the best practice to write code in Python is to use Function. You can modify your code as given below or You can use this function.
def charreplace(s):
return s.replace(s[0],'$')[:]
charreplace("restart")
#'$esta$t'
Hope this helpful.

Occurrence of a letter case sensitive

I am trying to find occurrence of letter 'b' and 'B'. the code that I have written works perfectly. Is there a better way that i can do this.
My code:
def count_letter_b(string):
#TODO: Your code goes here
a = int(string.count('B'))
b = int(string.count('b'))
return a + b
print count_letter_b("Bubble Bungle")
You can turn the string to uppercase (or lowercase), then count the occurrences:
string.upper().count('B')
So, overall, your code will look like this:
def count_letter_b(string):
return string.upper().count('B')
Note: no need to cast to int(..) as the result of str.count is already an int
Well if you only want to apply the same computation to a varying amount of letters you may want them to be arguments (count_letter(s, letters)), but anyway, here is a more functional example:
def count_letter_b(string):
return sum(map(string.count, 'Bb'))
This uses the str.count version that is bound to your input string instance.
Note that you're shadowing the name string if you use it as a parameter name.
You could do
# count in upper string, upper character
def countInvariantChars(c,s):
return s.upper().count(c.upper())
# list comprehensions + length
def countInvariantChars2(c,s):
return len([x for x in s if c.upper() == x.upper()])
# sum ones of list comprehension
def countInvariantChars3(c,s):
return sum([1 for x in s if c.upper() == x.upper()])
print(countInvariantChars("b","Bubble Bungle"))
print(countInvariantChars2("b","Bubble Bungle"))
print(countInvariantChars3("b","Bubble Bungle"))
Output (pyfiddle.io):
read-only#bash: 4
4
4
Use this:
def count_letter_b(string):
return string.lower().count('b')
print(count_letter_b(string))

How to add strings with each other during a loop?

For my programming class, I need to a create a program that takes in a string and two letters as an argument. Whenever the first letter appears in the string, it is replaced with the second letter. I can do this by making the final string into a list. However, our professor has stated that he wants it to be a string, not a list. The code shown below is what I used to make the program work if the final result was to appear in a list.
def str_translate_101(string, x, y):
new_list = []
for i in string:
if i == x:
new_list.append(y)
if i != x:
new_list.append(i)
return new_list
I tried to make one where it would output a string, but it would only return the first letter and the program would stop (which I'm assuming happens because of the "return")
def str_translate_101(string, old, new):
for i in string:
if i == old:
return new
else:
return i
I then tried using the print function, but that didn't help either, as nothing was outputted when I ran the function.
def str_translate_101(string, old, new):
for i in string:
if i == old:
print(new)
else:
print(i)
Any help would be appreciated.
An example of how the result should work when it works is like this:
str_translate_101('abcdcba', 'a', 'x') ---> 'xbcdcbx'
You can use join to merge a list into a string:
def str_translate_101(string, x, y):
new_list = []
for i in string:
if i == x:
new_list.append(y)
else:
new_list.append(i)
return ''.join(new_list)
or use the one-liner
str_tranlsate_101 = str.replace
The simplest solution would be, instead of storing the character in a list you can simply declare an empty string and in the 'if' block append the character to the string using the augmented '+=' operator. E.g.
if i == x:
concat_str += y
As for the return, basically, it will break out of the for loop and return to where the function was called from. This is because it only has 1 objective, which once achieved it will not bother to process any further code and simply go back to where the function was called from.

Categories

Resources