Match integers to letters in Python - python

I have a string of integers like '32102739' and a dictionary like {32: 'a', '739': 'b', '102': 'c'}. Given that string, I want to return the output string 'acb'.
I am trying
def change(string_, letters, length=1):
if string_[:length] in letters:
return letters[string_[:length]]
else:
return change(string_, letters, length+1)
I am having trouble with checking until the first letter is found, and then continuing to check for the next letter.

Assuming that there is no ambiguity (and that the numbers don't overlap), one could proceed as:
s = '32102739'
d = {'32': 'a', '739': 'b', '102': 'c'}
ret = ''
#start at the beginning of the string
pos, N = 0, len(s)
while pos < N:
#start at the current position pos and keep
#adding characters until a match is found
found = False
for i in range(pos+1, N+1):
#t = int(s[pos:i])
t = s[pos:i]
if t in d:
ret += d[t]
found = True
break
#if no match is found, signal an "invalid" input
if not found: raise ValueError
#update current position in the string
pos = i
print(ret) #gives 'acb'

Your code could work with just a few minor tweaks (at least, it will work for non-overlapping dictionary keys). You just need to change your successful match case so that you recurse on the rest of the string, after translating a prefix. You'll also need to add a base case, so that the recursion stops when the matching fails, or there's nothing left to match.
def change(string_, letters, length=1):
if len(string_) < length: # base case
return "" # you might want to test if string_ is empty and if not, raise an exception
elif string_[:length] in letters:
return letters[string_[:length]] + change(string_[length:], letters) # add recursion
else:
return change(string_, letters, length+1)
I'll end with a note that recursion is not as efficient in Python as it is in some other languages. You could restructure your function to use iteration instead of recursion, and it would probably be more efficient and "Pythonic". ewcz's answer is a good example of such a restructuring.

Related

Recursively searching for a string in a list of characters

I have a problem to solve which is to recursively search for a string in a list (length of string and list is atleast 2) and return it's positions. for example: if we had ab with the list ['a','b','c'], the function should return '(0,2)', as ab starts at index 0 and ends at 1 (we add one more).
if we had bc with the same list the function should return '(1,3)'.
if we had ac with the same list the function should return not found.
Note that I'm solving a bigger problem which is to recursively search for a string in a matrix of characters (that appears from up to down, or left to right only), but I am nowhere near the solution, so I'm starting by searching for a word in a row of a matrix on a given index (as for searching for a word in a normal list), so my code might have char_mat[idx], treat it as a normal list like ['c','d','e'] for example.
Note that my code is full of bugs and it doesn't work, so I explained what I tried to do under it.
def search_at_idx(search_word, char_mat, idx, start, end):
if len(char_mat[idx]) == 2:
if ''.join(char_mat[idx]) == search_word:
return 0,2
else:
return 'not found', 'not found'
start, end = search_at_idx(search_word, char_mat[idx][1:], idx, start+1, end)
return start, end
The idea of what I tried to do here is to find the base of the recursion (when the length of the list reaches 2), and with that little problem I just check if my word is equal to the chars when joined together as a string, and return the position of the string if it's equal else return not found
Then for the recursion step, I send the list without the first character, and my start index +1, so if this function does all the job for me (as the recursion hypothesis), I need to check the last element in the list so my recursion works. (but I don't know really if this is the way to do it since the last index can be not in the word, so I got stuck). Now I know that I made alot of mistakes and I'm nowhere near the correct answer,I would really appreciate any explanation or help in order to understand how to do this problem and move on to my bigger problem which is finding the string in a matrix of chars.
I've thrown together a little example that should get you a few steps ahead
char_mat = [['c', 'e', 'l', 'k', 'v'],]
search_word = 'lk'
def search_at_idx(search_word, char_mat, idx, start=0):
if len(char_mat[idx]) < len(search_word):
return 'not', 'found'
if ''.join(char_mat[idx][:len(search_word)]) == search_word:
return start, start+len(search_word)
char_mat[idx] = char_mat[idx][1:]
start, end = search_at_idx(search_word, char_mat, idx, start+1)
return start, end
print(search_at_idx(search_word, char_mat, 0))
To point out a few errors of yours:
In your recursion, you use char_mat[idx][1:]. This will pass a slice of the list and not the modified matrix. That means your next call to char_mat[idx] will check the letter at that index in the array. I'll recommend using the debugger and stepping through the program to check the contents of your variables
Instead of using start and end, you can always assume that the found word has the same length as the word you are searching for. So the distance you have to look is always start + len(search_word)
If you have any additional questions about my code, please comment.
Here's an example for list comprehension if that counts as loophole:
foundword = list(map("".join, list(zip(*([char_mat[idx][i:] + list(char_mat[idx][i-1]) for i in range(len(search_word))])))[:-1])).index(search_word)
print((foundword, foundword + len(search_word)) if foundword else 'Not found')
l = ["a","b","c"]
def my_indexes(pattern, look_list, indx_val):
if pattern == "".join(look_list)[:2]:
return indx_val, indx_val+1
else:
if len(look_list) == 2:
return None
return my_indexes(pattern, look_list[1:],indx_val+1)
print(my_indexes("bc",l,0))
Two options:
1.We find the case we are looking for, so the first two elements of our list are "ab", or
2. "a" and "b" are not first two elements of our list. call the same function without first element of the list,and increase indx_val so our result will be correct.We stop doing this when the len(list) = 2 and we didn't find a case. (assuming we're looking for length of 2 chars)
edit: for all lengths
l = ["a","b","c","d"]
def my_indexes(pattern, look_list, indx_val):
if pattern == "".join(look_list)[:len(pattern)]:
return indx_val, indx_val+len(pattern) # -1 to match correct indexes
else:
if len(look_list) == len(pattern):
return None
return my_indexes(pattern, look_list[1:],indx_val+1)
print(my_indexes("cd",l,0))

Passing modified list to each node of binary tree

I am writing a function to grow a tree:
def collect_append(collect,split):
collect.append(split)
return collect
def tree(string,passwords,collect): #collect is a list and passwords is also a list
matching_list = []
match = 0
if len(string)==0:
print(collect)
return 0
for j in passwords:
for i in range(min(len(j),len(string))):
if string[i]!=j[i]:
break
else :
matching_list.append(j)
match = match + 1
if match == 0:
return 1
else:
for split in matching_list:
x =tree(string.strip(split),passwords,collect_append(collect,split))
return x
My question is, for each split in matching_list(say two), I want to add different strings to the existing list at that point (i.e. I want two versions of list).
In this case the collect_append function I use is modifying the list in first iteration of the for loop and using the same for further iterations. What I want is to just modify the collect list just for the parameter and without permanently changing it. Is there a way to do this?
I see two serious errors in your code. First, this else clause is never executed:
for j in passwords:
for i in range(...):
if ...:
break
else:
...
Since the break is in the inner for loop, the outer for loop is never exited via a break so the else is never taken. Second, this doesn't do what you want:
string.strip(split)
You're trying to remove split from the beginning of string but you're removing all the letters in split from both ends of string, bruising it badly. Here's one way to do it correctly:
string[len(split):]
I'm going to go out on a limb, and rewrite your code to do what I think you want it to do:
def tree(string, passwords, collect):
length = len(string)
if length == 0:
return False
matching_list = []
for j in passwords:
i = min(len(j), length)
if string[:i] == j[:i]:
matching_list.append(j)
if not matching_list:
return False
result = False
for split in matching_list:
local_collection = list([split])
if split == string or tree(string[len(split):], passwords, local_collection):
collect.append(local_collection)
result = True
return result
collection = []
print(tree('dogcatcher', ['cat', 'catch', 'cher', 'dog', 'dogcat', 'dogcatcher', 'er'], collection))
print(collection)
OUTPUT
% python3 test.py
True
[['dog', ['cat', ['cher']], ['catch', ['er']]], ['dogcat', ['cher']], ['dogcatcher']]
%
Giving you a tree of all the ways to assemble string from the words in passwords.

How do I take an input string and count the number of characters in it by maintaining the order?

my_string = "aaabbcccaa"
required_output = "a3b2c3a2"
Please do not provide the code, I would like to try it myself, Please suggest me an approach with which i can get the required output.
As suggested by #ggradnig I've tried the below code.
def count_string(s):
current_char= s[0]
counter =0
result_string = ''
for i in range(len(s)):
if s[i] == current_char:
counter+=1
if s[i] != current_char:
result_string=result_string+current_char+str(counter)
current_char = s[i]
counter = 1
continue
result_string=result_string+current_char+str(counter)
return result_string
given_string=count_string("aabbbccccaa")
print(given_string)
Please suggest changes to improve the above code
Declare a variable for the current character you are counting and a counter variable. Set the "current character" to the first character of the string and the counter to 0. Also, declare an empty result String.
Now iterate over each character of the input String and check if it equals your "current character". If no, append this character to your result String, followed by the current value of the counter. Also, set the counter to 1 after appending and the "current character" to the character you are currently iterating over (i.e. the next character). If the character your currently iterating equals your "current character", increase your counter.
my_string = "aaabbcccaa" required_output= a3b2c3a2
You are counting consecutive characters, if the subsequent character is same, you increment a count variable, if the subsequent char is different, you move on and reinitialize count variable. Append count to the encountered character. When appending integer to a string you will have to typecast. Time complexity is O(n)
Use hashing.. with dictionaries in python. Make the character key and its occurrence a value.. And then traverse the dictionary... and took a empty string and append the both key and its value to the string.
EDIT
Now i think to change my approach instead of using sets under Dictionary and making things complex we can use simple approach. This program is working.
string2=""
string=input()
i=0
while(i<len(string)):
count=1
for j in range(i+1,len(string)):
if string[i]==string[j]:
count+=1
else:
break
string2=string2+string[i]
string2=string2+str(count)
i=i+count
print(string2)
The function groupby() in the module itertools makes this trivial. Read it's documentation where it shows the example (in comments):
>>> [list(g) for k, g in groupby('AAAABBBCCD')]
[['A', 'A', 'A', 'A'], ['B', 'B', 'B'], ['C', 'C'], ['D']]
From here you simply combine the first element of each sublist with the length of that sublist and you have your answer. Some string conversion and joining lists on empty strings necessary but basically a one-liner.
I don't understand why folks recommend the str.count() method. It combines two 'a' runs into one count which isn't desired and once you've broken the string into same letter substrings, len() can do the job.
Please suggest changes to improve the above code
from itertools import groupby
string = 'aaabbcccaa'
def count_string(s):
return ''.join(x[0] + str(len(x)) for x in (list(g) for _, g in groupby(s)))
print(string, '->', count_string(string))
Or if you wish to stay with your current algorithm, I suggest at least the following code cleanup:
def count_string(s):
counter = 0
result_string = ''
current_char = s[0]
for c in s:
if c == current_char:
counter += 1
else:
result_string += current_char + str(counter)
current_char = c
counter = 1
return result_string + current_char + str(counter)
You should use a for loop and string "count" method
and if you want me to post the code also tell me
Python has two method that you might fine useful.
I think python has count method that you could use or there is a length method that you can pass your string to and it out the length of the string.

How to append even and odd chars python

I want to convert all the even letters using one function and all the odd numbers using another function. So, each letter represents 0-25 correspsonding with a-z, so a,c,e,g,i,k,m,o,q,s,u,w,y are even characters.
However, only my even letters are converting correctly.
def encrypt(plain):
charCount = 0
answer=[]
for ch in plain:
if charCount%2==0:
answer.append(pycipher.Affine(7,6).encipher(ch))
else:
answer.append(pycipher.Affine(3,0).encipher(ch))
return ''.join(answer)
You never change charCount in your loop -- So it starts at 0 and stays at 0 which means that each ch will be treated as "even".
Based on your update, you actually want to check if the character is odd or even based on it's "index" in the english alphabet. Having some sort of mapping of characters to numbers is helpful here. You could build it yourself:
alphabet = 'abcde...' # string.ascii_lowercase?
mapping = {k: i for i, k in enumerate(alphabet)}
OR we can use the builtin ord noticing that ord('a') produces an odd result, ord('b') is even, etc.
def encrypt(plain):
answer=[]
for ch in plain:
if ord(ch) % 2 == 1: # 'a', 'c', 'e', ...
answer.append(pycipher.Affine(7,6).encipher(ch))
else: # 'b', 'd', 'f', ...
answer.append(pycipher.Affine(3,0).encipher(ch))
return ''.join(answer)
Your basic approach is to re-encrypt a letter each time you see it. With only 26 possible characters to encrypt, it is probably worth pre-encrypting them, then just performing a lookup for each character in the plain text. While doing that, you don't need to compute the position of each character, because you know you are alternating between even and odd the entire time.
import string
def encrypt(plain):
# True == 1, False == 0
fs = [pycipher.Affine(3,0).encipher,
pycipher.Affine(7,6).encipher]
is_even = True # assuming "a" is even; otherwise, just set this to False
d = dict()
for ch in string.ascii_lowercase:
f = fs[is_even]
d[ch] = f(ch)
is_even = not is_even
return ''.join([d[ch] for ch in plain])
You can also use itertools.cycle to simplify the alternation for you.
def encrypt(plain):
# again, assuming a is even. If not, reverse this list
fs = itertools.cycle([pycipher.Affine(3,0).encipher,
pycipher.Affine(7,6).encipher])
d = dict((ch, f(ch)) for f, ch in zip(fs, string.ascii_lowercase))
return ''.join([d[ch] for ch in plain])
This are my two cents on that. What #mgilson is proposing also works of course but not in the way you specified (in the comments). Try to debug your code in your head after writing it.. Go through the for loop and perform 1-2 iterations to see whether the variables take the values you intended them to. charCount is never reassigned a value. It is always 0. And, yes charCount += 1 would make it change but not in the way you want it to..
def encrypt(plain):
alphabet = 'abcdefghijklmnopqrwstuvwxyz'
answer = ''
for letter in plain:
try:
if alphabet.index(letter.lower()) % 2 == 0:
answer += pycipher.Affine(7, 6).encipher(letter)
else:
answer += pycipher.Affine(3, 0).encipher(letter)
except:
answer += letter
return answer
my_text = 'Your question was not very clear OP'
encripted_text = encrypt(my_text)
Also, i would not use ord(ch) because ord('a') = 97 and not 0 therefore odd instead of even.
Since your notion of even letter is based on the position of a character in the alphabet, you could use ord(), like this:
if ord(ch)%2==0:
Note that ord('a') and ord('A') are both odd, so that would make a go in the else part. If you want the opposite, then just negate the condition:
if ord(ch)%2!=0:

index of second repeated character in a string

I am trying a hangman code in python. For matching a character of a word , iam using index function to get the location of character.
Ex :word = 'COMPUTER'
user_input = raw_input('Enter a character :') # say 'T; is given here
if user_input in word:
print "\nThe Character %c is present in the word \n" %user_input
word_dict[word.index(user_input)] = user_input
#so the output will looks like
{0: '_', 1: '_', 2: '_', 3: '_', 4: '_', 5: 'T', 6: '_', 7: '_'}
Now , my problems comes when it comes with the repeated character.
# Another example
>>> 'CARTOON'.index('O')
4
For the second 'O', how to get its index. since i have used this 'index' logic, i am looking to continue on this way.
As per the str.index docs, signature looks like this
str.index(sub[, start[, end]])
The second parameter is the starting index to search from. So you can pass the index which you got for the first item + 1, to get the next index.
i = 'CARTOON'.index('O')
print 'CARTOON'.index('O', i + 1)
Output
5
The above code can be written like this
data = 'CARTOON'
print data.index('O', data.index('O') + 1)
You can even have this as a utility function, like this
def get_second_index(input_string, sub_string):
return input_string.index(sub_string, input_string.index(sub_string) + 1)
print get_second_index("CARTOON", "O")
Note: If the string is not found atleast twice, this will throw ValueError.
The more generalized way,
def get_index(input_string, sub_string, ordinal):
current = -1
for i in range(ordinal):
current = input_string.index(sub_string, current + 1)
else:
raise ValueError("ordinal {} - is invalid".format(ordinal))
return current
print get_index("AAABBBCCCC", "C", 4)
A perhaps more pythonic method would be to use a generator, thus avoiding the intermediate array 'found':
def find_indices_of(char, in_string):
index = -1
while True:
index = in_string.find(char, index + 1)
if index == -1:
break
yield index
for i in find_indices_of('x', 'axccxx'):
print i
1
4
5
An alternative would be the enumerate built-in
def find_indices_of_via_enumerate(char, in_string):
return (index for index, c in enumerate(in_string) if char == c)
This also uses a generator.
I then got curious as to perf differences. I'm a year into using python, so I'm only beginning to feel truly knowledgeable. Here's a quick test, with various types of data:
test_cases = [
('x', ''),
('x', 'axxxxxxxxxxxx'),
('x', 'abcdefghijklmnopqrstuvw_yz'),
('x', 'abcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvwxyz'),
]
for test_case in test_cases:
print "('{}', '{}')".format(*test_case)
print "string.find:", timeit.repeat(
"[i for i in find_indices_of('{}', '{}')]".format(*test_case),
"from __main__ import find_indices_of",
)
print "enumerate :", timeit.repeat(
"[i for i in find_indices_of_via_enumerate('{}', '{}')]".format(*test_case),
"from __main__ import find_indices_of_via_enumerate",
)
print
Which, on my machine results in these timings:
('x', '')
string.find: [0.6248660087585449, 0.6235580444335938, 0.6264920234680176]
enumerate : [0.9158611297607422, 0.9153609275817871, 0.9118690490722656]
('x', 'axxxxxxxxxxxx')
string.find: [6.01502799987793, 6.077538013458252, 5.997750997543335]
enumerate : [3.595151901245117, 3.5859270095825195, 3.597352981567383]
('x', 'abcdefghijklmnopqrstuvw_yz')
string.find: [0.6462750434875488, 0.6512351036071777, 0.6495819091796875]
enumerate : [2.6581480503082275, 2.6216518878936768, 2.6187551021575928]
('x', 'abcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvw_yzabcdefghijklmnopqrstuvwxyz')
string.find: [1.2539417743682861, 1.2511990070343018, 1.2702908515930176]
enumerate : [7.837890863418579, 7.791800022125244, 7.9181809425354]
enumerate() method is more expressive, pythonic. Whether or not perf differences matter depends on the actual use cases.
You've asked how to find the second occurrence, and gotten an excellent answer for that, generalized for any specific occurrence. What you'll realize you actually want though is all occurrences at once. Here's a method for that:
def find_characters(word, character):
found = []
last_index = -1
while True:
try:
last_index = word.index(character, last_index+1)
except ValueError:
break
else:
found.append(last_index)
return found
You can use the count method of the strings to find the number of occurrences of the user_input in the string. Then, use the str.index(sub,start) method for each occurrence of the user_input in the word and increment start by 1 each time so that you do not wind up getting the same index each time.
if user_input in word:
count=word.count(user_input)
a=word.index(user_input)
word_dict[word.index(a)]=user_input
for i in range(count-1):
a=word.index(user_input,a+1)
word_dict[word.index(a)]=user_input
This should really be a one-liner if you use filter because if you use index you will be forced to either iterate or use recursion. In this case, there is absolutely no need for either. You can just filter out the values that are relevant to you.
Using filter is easy. An example implementation is the following one-liner:
def f1(w, c):
return zip(* filter(lambda (x,y): x == c, zip(w, range(len(w))) ))[1]
f1('cartoon', 'o') # --> (4, 5)
You can always add error checking as in:
def f1(w, c) :
if c not in w: return ()
else: return zip(* filter(lambda (x,y): x == c, zip(w, range(len(w))) ))[1]
If the character isn't found in the string, you just get an empty tuple. Otherwise, you get all elements that match. If you want something generic, counting on the fact that there will only be one or two instances of a character is not the right way to go about it. For example:
In [18]: f1('supercalifragilisticexpialidocious', 'i')
Out[18]: (8, 13, 15, 18, 23, 26, 30)
Here is another Example.
a="samesame"
po=-1 # for this, po+1 is start from 0
for c in a:
if c=='s': # For example, I chose "S" what I want to find
po = a.index(c,po+1) # if you find first element 'C' then search again in next postion
print(po)
Apologies if this answer is not properly formatted or I've messed up somewhere as I am new here and this is my first post. I have used the following code in my own Hangman games to get the index of multiple repeating letters in a word and has worked great. Hopefully a newcomer will understand this ok.
a = "hangman" #the chosen word
length = len(a) #determines length of chosen word
for i in range(length) #this will loop through the code length number of times
if a[i] == "n": #n is the players guess. Checks if the letter is at index i
po = a.index("n", i) # po gets the index of the letter if previous line is true
print(po) #prints the position/s
Hope this helps someone!
def findcharpos(string, character, position=None):
array = []
index = -1
while True:
try:
index = string.index(character, index+1)
except ValueError:
break
else:
array.append(index)
if position == None and len(array) != 0:
return array
elif position > len(array):
raise ValueError(f"The character {character} does not occur {position}
times in the {string}")
else:
return array[position-1]
return array
msg = 'samesame'
print(msg.index('s', 2)) # prints the index of second 's' in the string.

Categories