HackerRank Game of Thrones - python

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]

Related

Minimum number of increments/decrements to transform string to a good form?

We are asked to modify a string by performing specific moves on it. Given a string of lowercase English characters ('a' - 'z'), two types of moves can be performed on any index, any number of times:
Decrement the character by 1. The letter 'a' cannot be decremented.
Increment the character by 1. Letter 'z' cannot be incremented.
Calculate the minimum number of moves required to modify the string to a good form. A good form string is one in which each character is adjacent to at least one equal character.
Example 1:
s = ‘aca’
Output: 2
Explanation: Decrement 'c' twice to get 'aaa'. A minimum of 2 moves is required.
Example 2:
s = 'abcdef'
Output: 3
Explanation: Decrement 'b' by 1 to become 'a'. Increment 'c' by 1 to become 'd'. Increment 'e' by 1 to become 'f'. This gets us "aaddff".
The first and last characters of the string only have one adjacent neighbor, and so they must be equal to that adjacent character.
What would be the best way to go about this?
I initially thought using two pointers, one for the left/start and right/end indices, and slowly moving them towards the center would be the best approach, using the thought that the edge of the string must be equal to the inner character in order to become equal.
But this doesn't give us a globally optimal solution. Would the best approach be something related to dynamic programming? If so, how would that look?
Yes, there’s a dynamic program, which can be extracted straightforwardly
from an algorithm that verifies a solution, implemented below in Python.
def has_good_form(s):
previous_letter = None
needs_adjacent_copy = False
for letter in s:
if letter == previous_letter:
needs_adjacent_copy = False
elif not needs_adjacent_copy:
previous_letter = letter
needs_adjacent_copy = True
else:
return False
return not needs_adjacent_copy
print(has_good_form("aca"))
print(has_good_form("aaa"))
print(has_good_form("abcdef"))
print(has_good_form("aaddff"))
has_good_form() remembers the previous letter and whether it is known
to have an adjacent copy, for a total of 26×2 = 54 states, plus the
starting state (so 55). The idea of the dynamic program is, for each
state and time, what’s the cheapest set of modifications that will put
has_good_form() in that state at that time? That looks something like
this (untested).
import collections
import math
import string
def distance(a, b):
return abs(ord(a) - ord(b))
def cheapest_good_form(s):
state_to_min_cost = {(None, False): 0}
for original_letter in s:
options = collections.defaultdict(list)
for state, min_cost in state_to_min_cost.items():
previous_letter, needs_adjacent_copy = state
for letter in string.ascii_lowercase:
cost = min_cost + distance(original_letter, letter)
if letter == previous_letter:
options[(letter, False)].append(cost)
elif not needs_adjacent_copy:
options[(letter, True)].append(cost)
state_to_min_cost = {state: min(costs) for (state, costs) in options.items()}
return min(
(
cost
for (
(_, needs_adjacent_copy),
cost,
) in state_to_min_cost.items()
if not needs_adjacent_copy
),
default=math.inf,
)
print(cheapest_good_form(""))
print(cheapest_good_form("a"))
print(cheapest_good_form("aca"))
print(cheapest_good_form("abcdef"))

Python algorithm in list

In a list of N strings, implement an algorithm that outputs the largest n if the entire string is the same as the preceding n strings. (i.e., print out how many characters in front of all given strings match).
My code:
def solution(a):
import numpy as np
for index in range(0,a):
if np.equal(a[index], a[index-1]) == True:
i += 1
return solution
else:
break
return 0
# Test code
print(solution(['abcd', 'abce', 'abchg', 'abcfwqw', 'abcdfg'])) # 3
print(solution(['abcd', 'gbce', 'abchg', 'abcfwqw', 'abcdfg'])) # 0
Some comments on your code:
There is no need to use numpy if it is only used for string comparison
i is undefined when i += 1 is about to be executed, so that will not run. There is no actual use of i in your code.
index-1 is an invalid value for a list index in the first iteration of the loop
solution is your function, so return solution will return a function object. You need to return a number.
The if condition is only comparing complete words, so there is no attempt to only compare a prefix.
A possible way to do this, is to be optimistic and assume that the first word is a prefix of all other words. Then as you detect a word where this is not the case, reduce the size of the prefix until it is again a valid prefix of that word. Continue like that until all words have been processed. If at any moment you find the prefix is reduced to an empty string, you can actually exit and return 0, as it cannot get any less than that.
Here is how you could code it:
def solution(words):
prefix = words[0] # if there was only one word, this would be the prefix
for word in words:
while not word.startswith(prefix):
prefix = prefix[:-1] # reduce the size of the prefix
if not prefix: # is there any sense in continuing?
return 0 # ...: no.
return len(prefix)
The description is somewhat convoluted but it does seem that you're looking for the length of the longest common prefix.
You can get the length of the common prefix between two strings using the next() function. It can find the first index where characters differ which will correspond to the length of the common prefix:
def maxCommon(S):
cp = S[0] if S else "" # first string is common prefix (cp)
for s in S[1:]: # go through other strings (s)
cs = next((i for i,(a,b) in enumerate(zip(s,cp)) if a!=b),len(cp))
cp = cp[:cs] # truncate to new common size (cs)
return len(cp) # return length of common prefix
output:
print(maxCommon(['abcd', 'abce', 'abchg', 'abcfwqw', 'abcdfg'])) # 3
print(maxCommon(['abcd', 'gbce', 'abchg', 'abcfwqw', 'abcdfg'])) # 0

Contradictory outputs in simple recursive function

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))

Count the number of occurrences of a given item in a (sorted) list?

I'm asked to create a method that returns the number of occurrences of a given item in a list. I know how to write code to find a specific item, but how can I code it to where it counts the number of occurrences of a random item.
For example if I have a list [4, 6 4, 3, 6, 4, 9] and I type something like
s1.count(4), it should return 3 or s1.count(6) should return 2.
I'm not allowed to use and built-in functions though.
In a recent assignment, I was asked to count the number of occurrences that sub string "ou" appeared in a given string, and I coded it
if len(astr) < 2:
return 0
else:
return (astr[:2] == "ou")+ count_pattern(astr[1:])
Would something like this work??
def count(self, item):
num=0
for i in self.s_list:
if i in self.s_list:
num[i] +=1
def __str__(self):
return str(self.s_list)
If this list is already sorted, the "most efficient" method -- in terms of Big-O -- would be to perform a binary search with a count-forward/count-backward if the value was found.
However, for an unsorted list as in the example, then the only way to count the occurrences is to go through each item in turn (or sort it first ;-). Here is some pseudo-code, note that it is simpler than the code presented in the original post (there is no if x in list or count[x]):
set count to 0
for each element in the list:
if the element is what we are looking for:
add one to count
Happy coding.
If I told you to count the number of fours in the following list, how would you do it?
1 4 2 4 3 8 2 1 4 2 4 9 7 4
You would start by remembering no fours yet, and add 1 for each element that equals 4. To traverse a list, you can use a for statement. Given an element of the list el, you can check whether it is four like this:
if el == 4:
# TODO: Add 1 to the counter here
In response to your edit:
You're currently testing if i in self.s_list:, which doesn't make any sense since i is an element of the list and therefore always present in it.
When adding to a number, you simply write num += 1. Brackets are only necessary if you want to access the values of a list or dictionary.
Also, don't forget to return num at the end of the function so that somebody calling it gets the result back.
Actually the most efficient method in terms of Big-O would be O(log n). #pst's method would result in O(log n + s) which could become linear if the array is made up of equal elements.
The way to achieve O(log n) would be to use 2 binary searches (which gives O(2log n), but we discard constants, so it is still O(log n)) that are modified to not have an equality test, therefore making all searches unsuccessful. However, on an unsuccessful search (low > high) we return low.
In the first search, if the middle is greater than your search term, recurse into the higher part of the array, else recurse into the lower part. In the second search, reverse the binary comparison.
The first search yields the right boundary of the equal element and the second search yields the left boundary. Simply subtract to get the amount of occurrences.
Based on algorithm described in Skiena.
This seems like a homework... anyways. Try list.count(item). That should do the job.
Third or fourth element here:
http://docs.python.org/tutorial/datastructures.html
Edit:
try something else like:
bukket = dict()
for elem in astr:
if elem not in bukket.keys():
bukket[elem] = 1
else:
bukket[elem] += 1
You can now get all the elements in the list with dict.keys() as list and the corresponding occurences with dict[key].
So you can test it:
import random
l = []
for i in range(0,200):
l.append(random.randint(0,20))
print l
l.sort()
print l
bukket = dict()
for elem in l:
if elem not in bukket.keys():
bukket[elem] = 1
else:
bukket[elem] += 1
print bukket

python look-and-say sequence improved

I would like to introduce look-and-say sequence at first. It goes like a = {1, 11, 21, 1211, 111221 ...
The system is it checks the previous digit and counts the numbers.
1 = one 1 (so = 11)
11 = two 1 (so = 21)
21 = one 2 one 1 (so = 1211)
As a rule of the sequence, no number can go beyond 3, so creating a translation table can fit in. But it is not semantic, I don't like it.
What I want is, a script which evaluates the given value and return a look-and-say-alike string.
However, to go beyond out limits, I want it to even evaluate chars, so it can return 1A2b41.
I have been trying to make it work for hours, the logic went bad and I am having a brainfreeze at the moment.
Here is the script that actually doesn't work(returns false results), but it can give you the idea, at least.
def seq(a):
k,last,result,a = 1,'','',str(a)
for i in range(len(a)):
if last==a[i]:k+=1
else:
result = result+str(k)+a[i]
k=1
last = a[i]
return result
You can use groupby, it's just what you want:
from itertools import groupby
def lookandsay(n):
return ''.join( str(len(list(g))) + k for k, g in groupby(n))
>>> lookandsay('1')
'11'
>>> lookandsay('1A2b41')
'111A121b1411'
>>> lookandsay(lookandsay('1A2b41'))
'311A1112111b111421'
groupby returns consecutive keys and groups from an iterable object. The key is a function computed for each element, or an identity function if not specified (as above). The group is an iterator - a new group is generated when the value of the key function changes. So, for instance, according to the documentation:
# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
I can see two issues with your code:
The result is expanded by k and a[i] although the counter k does not count chars a[i] but chars last. Replace a[i] by last here (you may not want to add anything in the first round).
After the loop you have to add the last value of the counter together with the last character again (this was not yet done), i.e. add another result = result+str(k)+last after the loop.
In total it looks like
def seq(a):
a = str(a)
k,last,result = 1,a[0],''
for i in range(1,len(a)):
if last==a[i]:k+=1
else:
result = result+str(k)+last
k=1
last = a[i]
result = result+str(k)+last
return result
I think part of why you got stumped is your use of meaningless variable names. You described the problem quite well and called it by name, but didn't even use that name for your function.
If you think of the string you start with as "look", and the one you end up with as "say", that is a start. result is probably fine but a and k have confused you. last is, I think, misleading, because it can mean either previous or final.
Also, Python's for is really foreach for a reason -- you're taking each character in the "look" one at a time, so do it explicitly in the loop.
def looksay(look):
look = str(look)
prev, count, say = look[0], 1, ''
for char in look[1:]:
if char == prev:
count += 1
continue
say += str(count) + prev
prev = char
count = 1
return say + str(count) + prev
The spacing is less important, but Python does have a standard coding style, and it does help readability to use it. The less mental time you have to spend parsing your code, the more focus you have for the problem.

Categories

Resources