Given a string with a series of Rs and Gs, search for a way to eliminate them by groups. You can ONLY eliminate if characters are grouped (at least 2).
Example 1: RGRRRGGGRR
v v v
RGRRRGGGRR = RGGGGRR = RRR = {empty}, thus there is a possible solution.
Example 2: GGGRGRGRG
v
GGGRGRGRG = RGRGRG, can no longer find groups, thus there is NO possible solution.
I made an initial recursive function but it ends with no solution even though there is a possible solution.
def fn(string, start):
l = len(string)
if l-start > 0:
counter, isPop = 0, False
if string[start] == 'R':
while counter + start < l and string[counter + start] == 'R': counter+=1
if counter > 1: # If there is the same letter adjacent to the current letter being checked, it will pop this group.
for i in range(counter):
string.pop(start)
isPop = True
if not isPop and start+counter == l: return False # Returns false if previous action did not pop and
else: return fn(string, 0 if isPop else start+counter) # Letter being checked is the last element (it means
# that there are still elements in the string that can
else: # no longer be grouped and eliminated.
while counter + start < l and string[counter + start] == 'G': counter+=1
if counter > 1:
for i in range(counter):
string.pop(start)
isPop = True
if not isPop and start+counter == len(string): return False
else: return fn(string, 0 if isPop else start+counter)
if string: return False
else: return True
ways = []
string = input()
for j in range(len(string)): # Iterates through every position in the list and calls the fn just to see if it is possible.
if True in ways: # If there is a possible solution, it breaks the loop and prints "possible"
break
ways.append(fn(list(string), j))
if True in ways: print("Possible")
else: print("Impossible")
Cases that my algo doesn't work (returns false but there is actually a way):
1. GGRRGRRRRGGGGRGRGG
v v v v v v
GGRRGRRRRGGGGRGRGG = GGRRGRRRRRGRGG = GGRRGGRGG = GGRRRGG = GGRRRGG = GGGG = {empty}
2. GRRRGRRRGRRRGRRRGR
v v v v v v
GRRRGRRRGRRRGRRRGR = GRRRGRRRGGRRRGR = GRRRGRRRGGGR = GRRRGRRRR = GGRRRR = RRRR = {empty}
You can use recursion with a generator. Additionally, applying itertools.groupby to produce groupings up front leads to a shorter solution:
from itertools import groupby as gb
def get_groups(s, chain=[]):
if not s:
yield chain+[s]
else:
r = [list(b) for _, b in gb(s)]
for i, a in enumerate(r):
if len(a) > 1:
t = ''.join(map(''.join, [*r[:i], *([] if i >= len(r) else r[i+1:])]))
yield from get_groups(t, chain+[s])
cases = ['RGRRRGGGRR', 'GGGRGRGRG', 'GGRRGRRRRGGGGRGRGG', 'GRRRGRRRGRRRGRRRGR']
for case in cases:
print(f'{case}: {any(get_groups(case))}')
Output:
RGRRRGGGRR: True
GGGRGRGRG: False
GGRRGRRRRGGGGRGRGG: True
GRRRGRRRGRRRGRRRGR: True
get_groups produces an empty list [] if there is no possible path whereby the input is completed reduced to an empty string by removing groupings, and a listing of all the possible paths to an empty string.
I believe your primary issue with respect to debugging this code is your coding style.
Besides being messy, you have ambiguous return situations; your 'R' and 'G' parallel blocks aren't identical; you fail to update the array length variable after popping the array; you appear to start the recursion at the wrong index.
The best I could come up with cleaning up your code is the following that passes one more case but still fails one:
def fn(characters, start):
length = len(characters)
if length - start > 0:
for character in ['R', 'G']:
counter = 0
isPop = False
if characters[start] == character:
while start+counter < length and characters[start+counter] == character:
counter += 1
if counter > 1:
# If there is the same letter adjacent to the current
# letter being checked, it will pop this group.
for _ in range(counter):
characters.pop(start)
isPop = True
length -= 1 # we've shortened the array
if not isPop and start+counter == length:
# Returns false if previous action did not pop and
# letter being checked is the last element (it means
# that there are still elements in characters that can
# no longer be grouped and eliminated.
return False
return fn(characters, start if isPop else start+counter)
if characters:
return False
return True
My own solution is akin to that of #Ajax1234 as far as groupby goes:
from itertools import groupby
def eliminate_groups(string):
if not string: # base case of recursion
return True
# "RGRRRGGGRR" -> ['R', 'G', 'RRR', 'GGG', 'RR']
groups = [''.join(g) for _, g in groupby(string)]
for index, group in enumerate(groups):
if len(group) < 2:
continue
reduced = groups[:index] + groups[index+1:] # a deficient copy
status = eliminate_groups(''.join(reduced))
if status: # one of the variants succeeded!
return status
return False # all of the variants failed!
if __name__ == '__main__':
strings = ["RGRRRGGGRR", "GGGRGRGRG", "GGRRGRRRRGGGGRGRGG", "GRRRGRRRGRRRGRRRGR"]
for string in strings:
print(string, eliminate_groups(string))
OUTPUT
> python3 test.py
RGRRRGGGRR True
GGGRGRGRG False
GGRRGRRRRGGGGRGRGG True
GRRRGRRRGRRRGRRRGR True
>
Related
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
I want say there is a string in the list with only one mismatch
def check(list, s):
n = len(list)
# If the array is empty
if (n == 0):
return False
for i in range(0, n, 1):
# If sizes are same
if (len(list[i]) != len(s)):
continue
diff = False
for j in range(0, len(list[i]), 1):
if (list[i][j] != s[j]):
# If first mismatch
if (diff == False):
diff = True
# Second mismatch
else:
diff = False
break
if (diff):
return True
return False
This code is okay but it works slowly. How can I make it faster using a dictionary?
You can use zip() to pair up each character of two strings and sum() to count the number of mismatches. The any() function will allow you to check for a match over every item in the list:
def check(sList,string):
return any(len(string)==len(s) and sum(a!=b for a,b in zip(s,string))==1
for s in sList)
check(["abcd","cdef","efgh"],"xfgh") # True
check(["abcd","cdef","efgh"],"abdc") # False
This translates to the following basic for-loop:
def check(sList,string):
for s in sList:
if len(string) !=len(s): continue
if sum(a!=b for a,b in zip(s,string)) == 1:
return True
return False
[EDIT] to make this go a bit faster, the string comparisons can use zip() as an iterator so that the comparison doesn't need to go to the end of every string:
def check(sList,string):
for s in sList:
if len(string) !=len(s): continue
mismatch = (a!=b for a,b in zip(s,string)) # non-matching iterator
if not any(mismatch): continue # at least one
if any(mismatch): continue # no more after that
return True
return False
There is a two input first input represent the index of the second input.I need to find the 'X' position of second input are same or not?
input:
123X56XX
Ahjutruu
Output:
True
Example:
X in input one at 4,7,8
position of 4,7,8 in second input is 'u' all are same so i need to print "true"
I tried using enumerate function but not came:
a="123X56XX"
b="Ahjutruu"
xi=[]
for i,val in enumerate(a):
if val=='X':
xi.append(i)
print(xi)
#next step only i dont know how to doo please help
Next i dont know how to check
Put all values of given indexes of xi in set, the length of this set must equal to 1:
xi = [i for i, c in enumerate(a) if c == 'X']
len(set([b[i] for i in xi])) == 1
you can do like this
a = "123X56XX"
b = "Ahjutruu"
status = len({j for i,j in zip(a,b) if i=='X'}) == 1
print(status)
An optional solution:
a="123X56XX"
b="Ahjutruu"
c = list([b[i] for i in range(len(a)) if a[i] == "X"]) # a sublist of the characters from b that correspond to "X" in a
s = set(c) # a set with the elements of c; a set has no duplicated elements, so if all of c's items are the same, s will only have one element
print(len(s) == 1)
the logic is added for matching the X places in a
a = "123X56XX"
b = "Ahjutruu"
xi = []
allSame = True
for i, val in enumerate(a):
if val == 'X':
xi.append(i)#saving the indices
allSame = len(a) == len(b) # length must be same
prev = None
for v in xi:
if not allSame:
break
if prev is None:
prev = b[v] # if previous value is not set setting it
else:
allSame = prev == b[v] # matching with previous value in b's list
print(allSame)
Minor addition to your code.
a="123X56XX"
b="Ahjutruu"
xi=[]
result = False
for i,val in enumerate(a):
if val=='X':
xi.append(i)
print(xi[0])
for index, value in enumerate(xi):
if index < len(xi) - 1:
if b[value] == b[xi[index+1]]:
result = True
else:
result = False
print(result)
prompt for isValidSubsequence
def isValidSubsequence(array, sequence):
index = -1
# issue is with initialising the index variable
if len(sequence)<=len(array):
for i in range(len(sequence)):
# i refers to the i in the sequence
if sequence[i] in array:
# causing a problem for repetitions such as array = [1,1,1,1,1] and subsequence = [1,1,1]
# array.index(sequence[i]) would call the first 1 instead of the second 1
if array.index(sequence[i]) > index:
index = array.index(sequence[i])
else:
return False
else:
return False
return True
else:
return False
How to solve this repeating 1s issue using my code?
The reason array.index(sequence[i]) calls the first 1 is because sequence[i] is 1 in your case, and that is the same as if you called array.index(1). The index function searches your array for 1 and as soon as it finds it, it returns its index, so it always finds the first 1 first and then stops looking and returns its index.
Try something like this, this should work:
def isValidSubsequence(array, sequence):
lastIndex = -1
if not len(array) >= len(sequence):
return False
for i in range(len(sequence)):
containsInt = False
for j in range(lastIndex + 1, len(array)):
if array[j] == sequence[i]:
containsInt = True
lastIndex = j
break
if not containsInt:
return False
return True
You are using lists, not arrays. list.index(x[, start[, end]]) can take positionals from where to where search - you could rewrite your algo to remember the last found position for each value and look for new values from that position.
BUT: this is irrelevant, you only need to concern yourself with how many of each element are in original and sequence and if one has at most equal of them...
The correct way to solve this ist to count the occurences of elements in the original, subtract the amount of occurences in the `sequence`` and evaluate your numbers:
If sequence is empty, contains elements not in original or contains more elements then inside original it can not be a subsequence:
from collections import Counter
def isValidSubsequence(array, sequence):
# check empty sequence
if not sequence:
return False
original = Counter(array)
# check any in seq that is not in original
if any(i not in original for i in sequence):
return False
# remove number of occurences in sequence from originals
original.subtract(sequence)
# check if too many of them in sequence
if any(v < 0 for v in original.values()):
return False
return True
Testcode:
source = [1,2,3,4]
test_cases = [[1], [2,4], [1,2,4] ]
neg_test_case = [[2,5], [9], []]
for tc in test_cases:
print(isValidSubsequence(source,tc), "should be True")
for ntc in neg_test_case:
print(isValidSubsequence(source,ntc), "should be False")
Output:
True should be True
True should be True
True should be True
False should be False
False should be False
False should be False
If you are not allowed to import, count yourself (expand this yourself):
# not efficient
counted = {d:original.count(d) for d in frozenset(original)}
# remove numbers
for d in sequence:
counted.setdefault(d,0)
counted[d] -= 1
This would be another way to answer the question.
def isValidSubsequence(array,sequence):
if len(array) >= len(sequence):
iter_sequence = iter(sequence)
last = next(iter_sequence)
total = 0
for i in array:
if i + total == last:
total += i
try:
last += next(iter_sequence)
except StopIteration:
return True
break
return False
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