Writing a recursive function - python

I am trying to write a recursive function in Python to count the number of pairs of repeated characters in a string. Example: "hellmoo" = 2
To get me started, I first tried to write an iterative version of the program. Here's my attempt:
counter = 0
string = input("Enter string:" )
for i in range(len(string)-1):
if string[i] == string[i+1]:
counter = counter+1
print (counter)
Now, I do not understand how I can write a recursive function from the above iterative program. I have tried to think of my base case as:
if string == "":
return (0)
I'm not sure if I am correct there. Could someone please help me do this? Thanks!

your logic assuming two characters in the string, so I think you need two base cases, for empty string and for one character string. (or one base case for string shorter then 2)
maybe something like this:
def count_doubles(string):
if len(string) < 2:
return 0
else:
if string[0] == string[1]:
return count_doubles(string[1:]) + 1
else:
return count_doubles(string[1:])
>>> count_doubles("hellmoo")
2
p.s.
I don't know why you want to do this with recursion, I dont think it's good idea for this task.
a more pythonic way to do it can be:
>>> string = "hellmoo"
>>> len(filter(lambda x:x[0]==x[1],zip(string[1:],string[:-1])))
2

This is kind of a silly assignment for Python, but in other languages that use a different fundamental data type, like a cons-list instead of an array, it makes sense.
You know how to write counter for a 2-element string.
For a 3-element string, you do the 2-element count on s[:2], then you do the 2-element count on s[1:].
For a 4-element string, you do the 2-element count on s[:2], then you do the 3-element count on s[1:].
For an N-element string, you do the 2-element count on s[:2], then you do the N-1-element count on s[1:].
Of course that leaves out the 0-element and 1-element cases, but you can just add 2 more base cases for that.

Defining a basic recursion with a wrapper:
def recursion(str, idx):
if (idx + 1 == len(str)):
return 0
if (str[idx+1] == str[idx]):
return 1 + recursion(str, idx + 1)
return recursion(str, idx + 1)
def count(str):
if len(str) == 0:
return 0
return recursion(str, 0)
>>> count("hellmoo")
2
>>> count("hellbb")
2
>>> count("hellbbvv")
3
>>> count("hh")
1
>>> count("")
0

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]

Finding index of given character in a string in Python with some default Parameter

This might be a silly question and perhaps might be one of those easiest question on SO. Consider the following code which tries to find the index of given character in a string:
def find(s ,ch, start=0):
index = start
while index <= end:
if s[index] == ch:
return index
index = index + 1
return -1
print(find("apple","p"))
This works fine. Now, in this code I want to add a default Parameter end which will tell the function till what length of string, we have to search in the given string. Like this:
def find(s, ch, start=0,end=len(s)):
index = start
while index <= end:
if s[index] == ch:
return index
index = index + 1
return -1
print(find("apple","p"))
However, when I run this code, I get the error in the line 1 of the above code:
NameError: Name s is not defined
I tried to read something about this in some textbook. I found that when function is defined, s is still undefined (For which I have no idea about why this is a case). Hence, len(s) is undefinable.
I know that there is a built in function which implements this but I want to write my own algorithm to do that.
Can anyone help or give hint?
A more pythonic choice for iterating a list would be the for loop since it is simpler and easier to read:
def find(s, ch):
index = None
for i in range(0, len(s)):
if s[i] == ch:
index = i + 1
return (index)
print(find("auhkle","a"))
print(find("auhkle","h"))
print(find("auhkle","e"))
Output:
1
3
6

Write a function that takes a string as an argument and displays the letters, one per line using loops in python

I'm learning to program and I'm using "how to think like an computer scientist" the above question is an exercise
This is the program without a function
fruit = "banana"
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
I want to put that into a function like
def tranversal(fruit):
index = 0
while index < len(fruit):
letter = fruit[index]
return letter
index += 1
print(tranversal("apple"))
However this is only printing the first letter of "apple" and if I use print statement instead of return I will get None.
I'm very confused and need help !!
Seems like you didn't understand the purpose of the return statement inside a function. You might want to read this answer first to make things clear.
Once you understand the difference between print() and return, you should define what your function needs to do. Does it need to return the answer or is printing it on the screen enough?
Assuming the latter, given that strings are iterable, a more pythonic way to do it would be:
def transversal(fruit):
for letter in fruit:
print(letter)
Note that since the function is not explicitly returning a value if you try something like:
foo = transversal("banana")
the variable foo will hold the value None.
If you want your function to return the answer and not print it, you could append each letter to an empty result string, with separators for each new line and after you are done with that, simply return result. It could be a good exercise, so you should give it a try :).
A simple solution:
print(*'banana', sep='\n')
Output:
b
a
n
a
n
a
With help of the star operator * you can split a list or a string into parts and and pass them as multiple arguments to function. So the expression print(*'abc') is equivalent to print('a', 'b', 'c').
If you use print in the function, then you dont need to use print when calling the function.
def tranversal(fruit):
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index += 1
tranversal("apple")
If you use a return statement inside of the while loop, then you will immediately leave the function (and return the first letter), and the while loop will not be executed for higher indices.
You can use this code snippet
def printAllChar(s):
for i in s:
print(i,end='\n')
//calling here...
printAllChar("ProgRank")
//output here...
P
r
o
g
R
a
n
k
For the purpose of understanding i wanted to do that exercise with a function, while loop and get a return value.
I've gotten help and i appreciate everyone, here is my code:
def `tranversal`(fruit):
result = ""
length = int(len(fruit))
index = 0
while index < length:
result += fruit[index]
index += 1
if index == length:
return "\n".join(result)
print(tranversal("string"))
You need to execute the statement using the function outside the function. Just shift return tranversal("apple") outside the function transversal like this:
def transversal(fruit):
index = 0
letters = ''
while index < len(fruit):
letters += fruit[index] + '\n'
index += 1
return letters
print(transversal("apple"))
Thank you #MykolaZotko for pointing out an error in the code that caused it to only print the first letter.

Python count number of chars in string

Input :
abbbbccdddaaabbbbeeff
Output :
ab4c2d3a3b4e2f2
I have tried like below,
string = 'abbbbccccd'
strList = list(string)
sum = 0
for i , s in enumerate(string):
# print (strList[i],strList[i+1])
if strList[i] == strList[i+1]:
sum = sum + 1
print(strList[i],'****',sum )
else:
sum = sum + 1
print(strList[i],'****',sum )
sum = 0
But unable to print the last element from list.
Is there any better way to do it without using any inbuilt functions?
Edit : I wanted to understand the logic of printing abb4c2.. that's why I mentioned without any inbuilt functions. Its ok to use inbuilt functions if the logic can be understandable.
In those problems, always keep current state (current character & current count). No need for indexes, simpler logic.
And in the end, don't forget to "flush" the current loop data, else you miss the last iteration.
My proposal:
s = "abbbbccdddaaabbbbeeff"
result = []
current = None
current_count = 0
for c in s:
if current == c:
current_count += 1
else:
if current_count > 1:
result.append(str(current_count))
current_count = 1
current = c
result.append(c)
# don't forget last iteration count
if current_count > 1:
result.append(str(current_count))
print("".join(result))
prints:
ab4c2d3a3b4e2f2
Okay, I know "".join(result) invokes a built-in function, but that's the most efficient way. You don't want to append character by character to create the string from the list.
Once you proved that you're mastering such algorithms, use built-ins like itertools.groupby to do such jobs. It's faster and bug-free (or even better: this other answer)
You could use more_itertools:
from more_itertools import run_length
s = "abbbbccdddaaabbbbeeff"
result = ""
for char, num in run_length.encode(s):
result += f"{char}{num if num != 1 else ''}"
print(result) #returns ab4c2d3a3b4e2f2
EDIT: missed the part about inbuilt functions. This uses an external library. Leaving it here because I find the initial problem very interesting.
You can use dictionaries
a='abbbbccdddaaabbbbeeff'
d=dict()
for i in a:
if i not in d:d[i]=1
else:d[i]+=1
for key,value in d.items():
print(key,value,sep='',end='')
output a4b8c2d3e2f2

Python - packing/unpacking by letters

I'm just starting to learn python and I have this exercise that's puzzling me:
Create a function that can pack or unpack a string of letters.
So aaabb would be packed a3b2 and vice versa.
For the packing part of the function, I wrote the following
def packer(s):
if s.isalpha(): # Defines if unpacked
stack = []
for i in s:
if s.count(i) > 1:
if (i + str(s.count(i))) not in stack:
stack.append(i + str(s.count(i)))
else:
stack.append(i)
print "".join(stack)
else:
print "Something's not quite right.."
return False
packer("aaaaaaaaaaaabbbccccd")
This seems to work all proper. But the assignment says that
if the input has (for example) the letter a after b or c, then
it should later be unpacked into it's original form.
So "aaabbkka" should become a3b2k2a, not a4b2k2.
I hence figured, that I cannot use the "count()" command, since
that counts all occurrences of the item in the whole string, correct?
What would be my options here then?
On to the unpacking -
I've thought of the basics what my code needs to do -
between the " if s.isalpha():" and else, I should add an elif that
checks whether or not the string has digits in it. (I figured this would be
enough to determine whether it's the packed version or unpacked).
Create a for loop and inside of it an if sentence, which then checks for every element:
2.1. If it has a number behind it > Return (or add to an empty stack) the number times the digit
2.2. If it has no number following it > Return just the element.
Big question number 2 - how do I check whether it's a number or just another
alphabetical element following an element in the list? I guess this must be done with
slicing, but those only take integers. Could this be achieved with the index command?
Also - if this is of any relevance - so far I've basically covered lists, strings, if and for
and I've been told this exercise is doable with just those (...so if you wouldn't mind keeping this really basic)
All help appreciated for the newbie enthusiast!
SOLVED:
def packer(s):
if s.isalpha(): # Defines if unpacked
groups= []
last_char = None
for c in s:
if c == last_char:
groups[-1].append(c)
else:
groups.append([c])
last_char = c
return ''.join('%s%s' % (g[0], len(g)>1 and len(g) or '') for g in groups)
else: # Seems to be packed
stack = ""
for i in range(len(s)):
if s[i].isalpha():
if i+1 < len(s) and s[i+1].isdigit():
digit = s[i+1]
char = s[i]
i += 2
while i < len(s) and s[i].isdigit():
digit +=s[i]
i+=1
stack += char * int(digit)
else:
stack+= s[i]
else:
""
return "".join(stack)
print (packer("aaaaaaaaaaaabbbccccd"))
print (packer("a4b19am4nmba22"))
So this is my final code. Almost managed to pull it all off with just for loops and if statements.
In the end though I had to bring in the while loop to solve reading the multiple-digit numbers issue. I think I still managed to keep it simple enough. Thanks a ton millimoose and everyone else for chipping in!
A straightforward solution:
If a char is different, make a new group. Otherwise append it to the last group. Finally count all groups and join them.
def packer(s):
groups = []
last_char = None
for c in s:
if c == last_char:
groups[-1].append(c)
else:
groups.append([c])
last_char = c
return ''.join('%s%s'%(g[0], len(g)) for g in groups)
Another approach is using re.
Regex r'(.)\1+' can match consecutive characters longer than 1. And with re.sub you can easily encode it:
regex = re.compile(r'(.)\1+')
def replacer(match):
return match.group(1) + str(len(match.group(0)))
regex.sub(replacer, 'aaabbkka')
#=> 'a3b2k2a'
I think You can use `itertools.grouby' function
for example
import itertools
data = 'aaassaaasssddee'
groupped_data = ((c, len(list(g))) for c, g in itertools.groupby(data))
result = ''.join(c + (str(n) if n > 1 else '') for c, n in groupped_data)
of course one can make this code more readable using generator instead of generator statement
This is an implementation of the algorithm I outlined in the comments:
from itertools import takewhile, count, islice, izip
def consume(items):
from collections import deque
deque(items, maxlen=0)
def ilen(items):
result = count()
consume(izip(items, result))
return next(result)
def pack_or_unpack(data):
start = 0
result = []
while start < len(data):
if data[start].isdigit():
# `data` is packed, bail
return unpack(data)
run = run_len(data, start)
# append the character that might repeat
result.append(data[start])
if run > 1:
# append the length of the run of characters
result.append(str(run))
start += run
return ''.join(result)
def run_len(data, start):
"""Return the end index of the run of identical characters starting at
`start`"""
return start + ilen(takewhile(lambda c: c == data[start],
islice(data, start, None)))
def unpack(data):
result = []
for i in range(len(data)):
if data[i].isdigit():
# skip digits, we'll look for them below
continue
# packed character
c = data[i]
# number of repetitions
n = 1
if (i+1) < len(data) and data[i+1].isdigit():
# if the next character is a digit, grab all the digits in the
# substring starting at i+1
n = int(''.join(takewhile(str.isdigit, data[i+1:])))
# append the repeated character
result.append(c*n) # multiplying a string with a number repeats it
return ''.join(result)
print pack_or_unpack('aaabbc')
print pack_or_unpack('a3b2c')
print pack_or_unpack('a10')
print pack_or_unpack('b5c5')
print pack_or_unpack('abc')
A regex-flavoured version of unpack() would be:
import re
UNPACK_RE = re.compile(r'(?P<char> [a-zA-Z]) (?P<count> \d+)?', re.VERBOSE)
def unpack_re(data):
matches = UNPACK_RE.finditer(data)
pairs = ((m.group('char'), m.group('count')) for m in matches)
return ''.join(char * (int(count) if count else 1)
for char, count in pairs)
This code demonstrates the most straightforward (or "basic") approach of implementing that algorithm. It's not particularly elegant or idiomatic or necessarily efficient. (It would be if written in C, but Python has the caveats such as: indexing a string copies the character into a new string, and algorithms that seem to copy data excessively might be faster than trying to avoid this if the copying is done in C and the workaround was implemented with a Python loop.)

Categories

Resources