Finding adjacent pairs of characters in a string using recursion - python

I have to write a program that uses a recursive function to count the number of pairs of repeated characters in a string, and pairs of characters cannot overlap.
Say, for instance, I input the string "Hello, Sabeena". I need the output to be "2", one for the pair of ls and one for the pair of es.
This is what I have tried, but there is no output.
message = input("Enter a message:\n")
pairs = 0
k = 0
if len(message) == k:
return("Number of pairs:",pairs)
else:
if message[k] == message[k+1]:
pairs = pairs + 1
k = k+1
else:
k = k+1
Ideally, the program should look like this:
Enter a message:
Hello, Sabeena
Number of pairs: 2
Can anybody suggest where I'm going wrong?

Put the recursive code in a function (it can't really be considered recursive until it is), and return a recursive call to the function if you haven't reached your base case (k == len(message) - 1), incrementing k each time.
def find_adjacent(message, pairs, k):
if k == len(message) - 1: #subtract one to avoid getting a string index out of range error
return("Number of pairs:", pairs)
else:
return find_adjacent(message, pairs+1 if message[k]==message[k+1] else pairs, k+1)
if __name__ == "__main__":
message = "message" # change to input("Enter a message:\n")
pairs = 0
k = 0
m, p = find_adjacent(message, pairs, k)
print m, p
The above prints
Number of pairs: 1
If you fancy it a bit less compact and quite a bit more readable:
def find_adjacent(message, pairs, k):
if k == len(message)-1:
return("Number of pairs:", pairs)
elif message[k] == message[k+1]:
if k == 0:
return find_adjacent(message, pairs+1, k+1) # first letter of message, all good
elif message[k] != message[k-1]:
return find_adjacent(message, pairs+1, k+1) # not first letter, and this pair hasn't been counted before
else:
return find_adjacent(message, pairs, k+1) # this sequence has already been counted
else:
return find_adjacent(message, pairs, k+1)

Here's a recursive function that passes a new copy of the message, each time shorter.
This is what most recursive functions do: not memory-efficient but solving the problem in simpler ways than loop-based approaches. That's not the case here of course, but this is just an exercise.
def count_adj(message, adj, c):
"""Recursively counts the number of adjacent characters"""
if len(message) < 1:
return adj
else:
if c == message[0]:
adj += 1
return count_adj(message[1:], adj, message[0])
tests = ("Hello, Sabeeenaa", "Hi", "h", "HH", "HHH", "", " ", " ")
for t in tests:
print t, ":", count_adj(t, 0, '')
Results:
Hello, Sabeeenaa : 4
Hi : 0
h : 0
HH : 1
HHH : 2
: 0
: 0
: 1

Related

Code for consecutive strings works but can't pass random tests

In this problem, I'm given an array(list) strarr of strings and an integer k. My task is to return the first longest string consisting of k consecutive strings taken in the array. My code passed all the sample tests from CodeWars but can't seem to pass the random tests.
Here's the link to the problem.
I did it in two days. I found the max consecutively combined string first. Here's the code for that.
strarr = []
def longest_consec(strarr, k):
strarr.append('')
length = len(strarr)
cons_list = []
end = k
start = 0
freq = -length/2
final_string = []
largest = max(strarr, key=len, default='')
if k == 1:
return largest
elif 1 < k < length:
while(freq <= 1):
cons_list.append(strarr[start:end])
start += k-1
end += k-1
freq += 1
for index in cons_list:
final_string.append(''.join(index))
return max(final_string, key=len, default='')
else:
return ""
Since that didn't pass all the random tests, I compared the combined k strings on both sides of the single largest string. But, this way, the code doesn't account for the case when the single largest string is in the middle. Please help.
strarr = []
def longest_consec(strarr, k):
strarr.append('')
length = len(strarr)
largest = max(strarr, key=len, default='')
pos = int(strarr.index(largest))
if k == 1:
return largest
elif 1 < k < length:
prev_string = ''.join(strarr[pos+1-k:pos+1])
next_string = ''.join(strarr[pos:pos+k])
if len(prev_string) >= len(next_string):
res = prev_string
else:
res = next_string
return res
else:
return ""
print(longest_consec(["zone", "abigail", "theta", "form", "libe"], 2))
Let's start from the first statement of your function:
if k == 1:
while(p <= 1):
b.append(strarr[j:i])
j += 1
i += 1
p += 1
for w in b:
q.append(''.join(w))
return max(q, key=len)
Here q is finally equal strarr so you can shorten this code to:
if k == 1:
return max(strarr, key=len)
I see that second statement's condition checks if k value is between 1 and length of string array inclusive:
elif k > 1 and k <= 2*a:
...
If you want no errors remove equality symbol, last element of every array has index lesser than its length (equal exactly length of it minus 1).
Ceiling and division is not necessary in a definition, so you can shorten this:
a = ceil(len(strarr)/2)
into this:
a = len(strarr)
then your elif statement may look like below:
elif 1 < k < a: # Same as (k > 1 and k < a)
...
again, I see you want to concatenate (add) the longest string to k next strings using this code:
while(p <= 1):
b.append(strarr[j:i])
j += k-1
i += k-1
p += 1
for w in b:
q.append(''.join(w))
return max(q, key=len)
the more clearer way of doing this:
longest = max(strarr, key=len) # Longest string in array.
index = 0 # Index of the current item.
for string in strarr:
# If current string is equal the longest one ...
if string == longest:
# Join 'k' strings from current index (longest string index).
return ''.join(strarr[index:index + k])
index += 1 # Increase current index.
And the last statement which is:
elif k > 2*a or k<1:
return ""
if all previous statements failed then value is invalid so you can instead write:
return "" # Same as with else.
Now everything should work. I advice you learning the basics (especially lists, strings and slices), and please name your variables wisely so they are more readable.
You can try this as well
this has passed all the test cases on the platform you suggested.
def longest_consec(strarr, k):
i = 0
max_ = ""
res = ""
if (k<=0) or (k>len(strarr)):
return ""
while i<=(len(strarr)-k):
start = "".join(strarr[i:i+k])
max_ = max(max_, start, key=len)
if max_==start:
res=strarr[i:i+k]
i+=1
return max_
#output: ["zone", "abigail", "theta", "form", "libe", "zas", "theta", "abigail"], 2 -> abigailtheta
#output: ["zones", "abigail", "theta", "form", "libe", "zas", "theta", "abigail"],2 -> zonesabigail

Find the length of the palindrome that can be made from a string in Python

I try to make program to find the length of the palindrome that can be made from a string in Python, but I got wrong result.
This is my program but I still get wrong results, maybe anyone can help me to fixed this.
from typing import Counter
def checkpalindrom(s):
mid = len(s)/2
end = len(s)-1
i = 0
while i<= mid:
if s[i]!= s[end-i]:
return False
return True
def deletepalindrom(s):
if deletepalindrom(s)==True:
return 0
min = 100
x = 0
while x < len(s):
temp = s
temp.remove(1)
Counter = deletepalindrom(temp)+1
if Counter<=min:
min = Counter
n = int(input())
for y in range(n):
s = input()
print(len(s)- deletpalindrom(s))
I got a redline in line print(len(s)- deletpalindrom(s)) to show the length and also in line temp.remove(1) got 'str' object has no attribute 'remove'.
You can do the following to achieve what you want:
from itertools import permutations
def get_len_palindrome(string):
all_permutations = [''.join(p) for p in permutations(string)]
lst_palindrome_lengths = []
for permutation in all_permutations:
for i in range(len(permutation)+1):
if permutation[:i] == permutation[:i][::-1]:
lst_palindrome_lengths.append(i)
lst_palindrome_lengths.sort(reverse=True)
return lst_palindrome_lengths[0]
n = int(input())
for y in range(n):
s = input()
print(get_len_palindrome(s))
The function get_len_palindrome(string) first utilises the permutations method of the itertools module to obtain all permutations of a given string, then, drawing from a pythonic way to check for palindromes suggested here, uses the permutations obtained this way to determine the lengths of palindromes constructed from this string and returns the maximum length.
It is helpful to make the following observations:
In a palindrome p, at most one letter appears an odd number of times.
The maximum length of a palindrome formed using letters in a string w is fully determined by the letter "frequencies" of the string w. For example, the answer for aab is the same as the answer for bba.
With the observations in mind, here is one solution using collections.Counter.
from collections import Counter
xs = ['wewewe', 'pelatnas', 'kedua', 'di', 'jogja']
ys = [5, 3, 1, 1, 3]
def pal_len(s):
result = 0
# letter frequencies
ctr = Counter(s)
# False only if each letter appears an even number of times
have_spare = False
for k, v in ctr.items():
# letter k appears v times
# if v is even, we can add v characters to the palindrome
# if v is odd, we can add v - v%2 characters to the palindrome
remainder = v % 2
result += v - remainder
if remainder:
have_spare = True
# at this point, have_spare == True whenever
# there is a character that appears an odd number of times in s
# we can insert such a character in the middle of the palindrome
return result + have_spare
assert all(pal_len(x) == y for x, y in zip(xs, ys))

Find the largest possible substring with k unique letters. (recursive)

I am trying to find max substring possible with k unique letters. Is there a way i can do it recursive by string partition?
My idea is to partition a string by cutting the last characters and if i find the first substring that contains k unique letters i return it.
For example k = 2, string = "abccd"
abccd ->
abcc, bccd ->
abc,bcc,bcc,ccd -> return bcc
def unique_l(sub, k):
u=0
visited = set()
for ch in sub:
if ch not in visited:
visited.add(ch)
u += 1
if u < k:
return -1
elif u == k:
return 1
else:
return 0
def find_sub(string,k):
if unique_l(string,k) == 1:
return string
if unique_l(string,k) == -1:
return "Not Found"
find_sub(string[0:len(string)-1],k) # Left
find_sub(string[1:len(string)],k) # Right
I know that i can do it in O(n) time using iteration but is there a way to do it recursive?
You can use recursion with a generator:
from collections import Counter
def group(d, k):
for i in range(len(d)):
for b in range(i, len(d)):
if len(set((_r:=d[i:b]))) == k:
yield _r
yield from group(_r, k)
r = max(group("abccd", 2), key=len)
Output:
'bcc'

Divide and Conquer. Find the majority of element in array

I am working on a python algorithm to find the most frequent element in the list.
def GetFrequency(a, element):
return sum([1 for x in a if x == element])
def GetMajorityElement(a):
n = len(a)
if n == 1:
return a[0]
k = n // 2
elemlsub = GetMajorityElement(a[:k])
elemrsub = GetMajorityElement(a[k:])
if elemlsub == elemrsub:
return elemlsub
lcount = GetFrequency(a, elemlsub)
rcount = GetFrequency(a, elemrsub)
if lcount > k:
return elemlsub
elif rcount > k:
return elemrsub
else:
return None
I tried some test cases. Some of them are passed, but some of them fails.
For example, [1,2,1,3,4] this should return 1, buit I get None.
The implementation follows the pseudocode here:
http://users.eecs.northwestern.edu/~dda902/336/hw4-sol.pdf
The pseudocode finds the majority item and needs to be at least half. I only want to find the majority item.
Can I get some help?
Thanks!
I wrote an iterative version instead of the recursive one you're using in case you wanted something similar.
def GetFrequency(array):
majority = int(len(array)/2)
result_dict = {}
while array:
array_item = array.pop()
if result_dict.get(array_item):
result_dict[array_item] += 1
else:
result_dict[array_item] = 1
if result_dict[array_item] > majority:
return array_item
return max(result_dict, key=result_dict.get)
This will iterate through the array and return the value as soon as one hits more than 50% of the total (being a majority). Otherwise it goes through the entire array and returns the value with the greatest frequency.
def majority_element(a):
return max([(a.count(elem), elem) for elem in set(a)])[1]
EDIT
If there is a tie, the biggest value is returned. E.g: a = [1,1,2,2] returns 2. Might not be what you want but that could be changed.
EDIT 2
The pseudocode you gave divided into arrays 1 to k included, k + 1 to n. Your code does 1 to k - 1, k to end, not sure if it changes much though ? If you want to respect the algorithm you gave, you should do:
elemlsub = GetMajorityElement(a[:k+1]) # this slice is indices 0 to k
elemrsub = GetMajorityElement(a[k+1:]) # this one is k + 1 to n.
Also still according to your provided pseudocode, lcount and rcount should be compared to k + 1, not k:
if lcount > k + 1:
return elemlsub
elif rcount > k + 1:
return elemrsub
else:
return None
EDIT 3
Some people in the comments highligted that provided pseudocode solves not for the most frequent, but for the item which is present more that 50% of occurences. So indeed your output for your example is correct. There is a good chance that your code already works as is.
EDIT 4
If you want to return None when there is a tie, I suggest this:
def majority_element(a):
n = len(a)
if n == 1:
return a[0]
if n == 0:
return None
sorted_counts = sorted([(a.count(elem), elem) for elem in set(a)], key=lambda x: x[0])
if len(sorted_counts) > 1 and sorted_counts[-1][0] == sorted_counts[-2][0]:
return None
return sorted_counts[-1][1]

Check string to see if it is a palindrome of any of it's permutations

I've created a function that does this successfully(I'm pretty sure), but I'm worried about the efficiency in part of it. I have two nested for loops that I think make this algorithm's worst case around O(n^2). Is there any way I can improve this?
def palindrome(string):
s = [c.replace(' ', '') for c in string]
merged = "".join(s)
srt = sorted(merged)
dic = {}
singles = 0
for i in srt:
if i not in dic:
dic[i] = 1
else:
# Worried about this second loop having to run for every i in srt
for key, value in dic.items():
if key == i:
dic[key] = 2
for key, value in dic.items():
if value == 1:
singles += 1
if singles > 1:
return False
else:
return True
What you need is to find out if there is at most one "single" letter (and others are paired). Thus we count the letters with collections.Counter and ensure that only 0 or 1 of them has odd count:
from collections import Counter
def has_palindrome(string):
return sum(v % 2 for v in Counter(string).values()) <= 1
print(has_palindrome('abcabcc')) # True
print(has_palindrome('abc')) # False
As it tests if any permutation of the letters in the string could be a palindrome.
I would suggest:
from collections import Counter
def palindrome(string):
s = string.replace(' ', '')
return not sum(v % 2 == 1 for k,v in Counter(string)) > 1
This checks of the sum of the number of counts of character occurrences that are odd is not greater than one.
Above code will fail if there are three occurrences of certain character. Try abcabccddd
Slight modification to fix the issue:
def checkPalindromPermutation(word):
values = collections.Counter(word).values()
if sum((v % 2) for v in values) > 1 or any(v > 2 for v in values):
return False
return True

Categories