Trying to solve a number to mnemonics problem using recursion - python

Given a stringified phone number of non-zero length, write a function that returns all mnemonics for this phone number in any order.
`
def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}
if idx==len(phoneNumber):
return Mnemonics
else:
new_Mnemonics=[]
for letter in number_lookup[phoneNumber[idx]]:
for mnemonic in Mnemonics:
new_Mnemonics.append(mnemonic+letter)
phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
`
If I use the input "1905", my function outputs null. Using a print statement right before the return statement, I can see that the list Mnemonics is
['1w0j', '1x0j', '1y0j', '1z0j', '1w0k', '1x0k', '1y0k', '1z0k', '1w0l', '1x0l', '1y0l', '1z0l']
Which is the correct answer. Why is null being returned?
I am not very good at implementing recursion (yet?), your help is appreciated.

There are different recursive expressions of this problem, but the simplest to think about when you are starting out is a "pure functional" one. This means you never mutate recursively determined values. Rather compute fresh new ones: lists, etc. (Python does not give you a choice regarding strings; they're always immutable.) In this manner you can think about values only, not how they're stored and what's changing them, which is extremely error prone.
A pure-functional way to think about this problem is this:
If the phone number is the empty string, then the return value is just a list containing the empty string.
Else break the number into its first character and the rest. Recursively get all the mnemonics R of the rest. Then find all the letters corresponding to the first and prepend each of these to each member of R to make a new string (This is called a Cartesian cross product, which comes up often in recursion.) Return all of those strings.
In this expression, the pure function has the form
M(n: str) -> list[str]:
It's accepting a string of digits and returning a list of mnemonics.
Putting this thought into python is fairly simple:
LETTERS_BY_DIGIT = {
'0':['0'],
'1':['1'],
'2':['a','b','c'],
'3':['d','e','f'],
'4':['g','h','i'],
'5':['j','k','l'],
'6':['m','n','o'],
'7':['p','q','r','s'],
'8':['t','u','v'],
'9':['w','x','y','z'],
}
def mneumonics(n: str):
if len(n) == 0:
return ['']
rest = mneumonics(n[1:])
first = LETTERS_BY_DIGIT[n[0]]
rtn = [] # A fresh list to return.
for f in first: # Cartesian cross:
for r in rest: # first X rest
rtn.append(f + r); # Fresh string
return rtn
print(mneumonics('1905'))
Note that this code does not mutate the recursive return values rest at all. It makes a new list of new strings.
When you've mastered all the Python idioms, you'll see a slicker way to code the same thing:
def mneumonics(n: str):
return [''] if len(n) == 0 else [
c + r for c in LETTERS_BY_DIGIT[n[0]] for r in mneumonics(n[1:])]
Is this the most efficient code to solve this problem? Absolutely not. But this isn't a very practical thing to do anyway. It's better to go for a simple, correct solution that's easy to understand rather than worry about efficiency before you have a solid grasp of this way of thinking.
As others have said, using recursion at all on this problem is not a great choice if this were a production requirement.

The correct list (Mnemonics) was generated for the deepest call of the recursion. However, it was not passed back to previous calls.
To fix this, the Mnemonics not only needs to be returned in the "else" block, but it also needs to be set to equal the output of the recursive function phone Number Mnemonics.
def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}
print(idx, len(phoneNumber))
if idx==len(phoneNumber):
pass
else:
new_Mnemonics=[]
for letter in number_lookup[phoneNumber[idx]]:
for mnemonic in Mnemonics:
new_Mnemonics.append(mnemonic+letter)
Mnemonics=phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
return Mnemonics
I still feel that I'm lacking sophistication in my understanding of recursion. Advice, feedback, and clarifications are welcome.

Related

Transliterate sentence written in 2 different scripts to a single script

I am able to convert an Hindi script written in English back to Hindi
import codecs,string
from indic_transliteration import sanscript
from indic_transliteration.sanscript import SchemeMap, SCHEMES, transliterate
def is_hindi(character):
maxchar = max(character)
if u'\u0900' <= maxchar <= u'\u097f':
return character
else:
print(transliterate(character, sanscript.ITRANS, sanscript.DEVANAGARI)
character = 'bakrya'
is_hindi(character)
Output:
बक्र्य
But If I try to do something like this, I don't get any conversions
character = 'Bakrya विकणे आहे'
is_hindi(character)
Output:
Bakrya विकणे आहे
Expected Output:
बक्र्य विकणे आहे
I also tried the library Polyglot but I am getting similar results with it.
Preface: I know nothing of devanagari, so you will have to bear with me.
First, consider your function. It can return two things, character or None (print just outputs something, it doesn't actually return a value). That makes your first output example originate from the print function, not Python evaluating your last statement.
Then, when you consider your second test string, it will see that there's some Devanagari text and just return the string back. What you have to do, if this transliteration works as I think it does, is to apply this function to every word in your text.
I modified your function to:
def is_hindi(character):
maxchar = max(character)
if u'\u0900' <= maxchar <= u'\u097f':
return character
else:
return transliterate(character, sanscript.ITRANS, sanscript.DEVANAGARI)
and modified your call to
' '.join(map(is_hindi, character.split()))
Let me explain, from right to left. First, I split your test string into the separate words with .split(). Then, I map (i.e., apply the function to every element) the new is_hindi function to this new list. Last, I join the separate words with a space to return your converted string.
Output:
'बक्र्य विकणे आहे'
If I may suggest, I would place this splitting/mapping functionality into another function, to make things easier to apply.
Edit: I had to modify your test string from 'Bakrya विकणे आहे' to 'bakrya विकणे आहे' because B wasn't being converted. This can be fixed in a generic text with character.lower().

All of the lists are coming out empty?

I dont know if I am doing something wrong with the way I am laying out the functions or what. Basically I am trying to send a list of tickers to a database. I am taking the difference of two list so that the one that I send doesnt have the same tickers as the previous send. Can anyone help me?
tickList=[]
compareList=[]
newestList=[]
def searchTwit():
tweets = api.search("#stocks",count=100)
return tweets
def Diff(li1, li2):
return (list(list(set(li1)-set(li2)) + list(set(li2)-set(li1))))
def getTicker(tweets,list1,list2,anyList):
for tweet in tweets:
if "$" in tweet.text:
x = tweet.text.split()
for i in x:
if i.startswith("$") and i[1].isalpha():
i.strip(".")
i.upper()
list1.append(i)
anyList = Diff(list1,list2)
#print(newestList)
list2=list1
#print(list2)
list1.clear()
#print(list1)
return(anyList)
def retrieveTickers(list):
for i in list:
cursor.execute('INSERT INTO master.dbo.TickerTable (TickerName) VALUES (?);', (i))
conn.commit()
while True:
sleep(60 - time() %60)
print(f'This is ticklist {tickList}')
print(f'This is compareList{compareList}')
print(f'This is newestList {newestList}')
full_tweets=searchTwit()
getTicker(full_tweets,tickList,compareList,newestList)
retrieveTickers(newestList)
There are a few places that seem incorrect.
Python strings are immutable so this code block isn't doing what you think.
i.strip(".")
i.upper()
list1.append(i)
The string manipulation functions return new strings so you need to do this:
list1.append(i.strip(".").upper())
Its super unclear what you want retrieveTickers to do since its actually writing to a database. The function verb should match the action.
The diff function is convoluted. It can be simplified to
(set(li1) | set(li2)) - (set(li1) & set(li2))
but I don't see where you need these to be ordered so I would recommend changing the type you standardize on to set then diff can be written as:
li1.symmetric_difference(li2)
which probably doesn't need to be a function.
This block:
full_tweets=searchTwit()
getTicker(full_tweets,tickList,compareList,newestList)
retrieveTickers(newestList)
is also really really hard to follow. I think you are trying to manipulate the lists within the getTicker function. I strongly recommend not doing this. Instead return the information you need and if you need more than one data structure return them as a tuple. If I understand this correctly your function signature should probably look more like this:
new_tickers, already_seen_data = \
getTicker(full_tweets, already_seen_data)

Kaggle Python course Exercise: Strings and Dictionaries Q. no. 2

Here's the question,
A researcher has gathered thousands of news articles. But she wants to focus her attention on articles including a specific word. Complete the function below to help her filter her list of articles.
Your function should meet the following criteria:
Do not include documents where the keyword string shows up only as a part of a larger word. For example, if she were looking for the keyword “closed”, you would not include the string “enclosed.”
She does not want you to distinguish upper case from lower case letters. So the phrase “Closed the case.” would be included when the keyword is “closed”
Do not let periods or commas affect what is matched. “It is closed.” would be included when the keyword is “closed”. But you can assume there are no other types of punctuation.
Here's my ans(I want to solve this just using loops and ifs):
def word_search(doc_list, keyword):
"""
Takes a list of documents (each document is a string) and a keyword.
Returns list of the index values into the original list for all documents
containing the keyword.
Example:
doc_list = ['The Learn Python Challenge Casino', 'They bought a car, and a horse', 'Casinoville?']
word_search(doc_list, 'casino')
>>> [0]
"""
#non-course provided and my own code starts here.
k=0
print(doc_list,keyword)
for string in doc_list:
print(string)
for char in string:
if char.upper()==keyword[0] or char.lower()==keyword[0]:
print(char,string[string.index(char)-1])
if (string[string.index(char)-1]==" " or string[string.index(char)-1]=="" or string[string.index(char)-1]==".") and (string[string.index(char)+len(keyword)]==" " or string[string.index(char)+len(keyword)]=="" or string[string.index(char)+len(keyword)]=="."):
print(string[string.index(char)-1])
for k in range(len(keyword)):
print(k)
if string[string.index(char)+k].upper()==keyword[k] or string[string.index(char)+k].lower()==keyword[k]:
c=c+k
if len(c)==len(keyword):
x=[doc_list.index(string)]
return x
But after running the check code:
q2.check() #returns,
Incorrect: Got a return value of None given doc_list=['The Learn Python Challenge Casino', 'They bought a car, and a horse', 'Casinoville?'], keyword='casino', but expected a value of type list. (Did you forget a return statement?)
Here's what gets printed out after executing the code:
['The Learn Python Challenge Casino', 'They bought a car, and a horse',
'Casinoville?'] casino
The Learn Python Challenge Casino
C
C
They bought a car, and a horse
c
Casinoville?
C ?
The code is compiling successfully without syntax and other explicit errors. But I can't find any implicit bugs that's generating a wrong ans after struggling for 5+ hrs. please help!
If I remember correctly Kaggle courses also provide you with the solution which is the solution you should understand and use moving forward. Your code has many conditions and it will be tough to determine which of these conditions is not implemented correctly. Might as well check Kaggles' solution because you cant use this moving forward. Also the solution you have has a nested for-loop checking each letter one-by-one. That is extremely inefficient. Nice beginners attempt though :)
Here is the solution using regex
import re
def word_search(documents, keyword):
res=[]
for i,j in enumerate(documents):
if re.findall('\\b'+keyword+'\\b',j,flags=re.IGNORECASE):
res.append(i)
return res
As stated by the answer, your function should return a list. You are instead returning a None value, because at some points in your nested ifs you are going at the end of your function, in which is not specified any return. When you don't specify any return keyword at the end of your function, it will return None as default
By the way, python offers a lot of utils libraries, for example the str.index() method that return the string index if found in the original string
This I think is a better development of your solution:
def word_search(doc_list, keyword):
"""
Takes a list of documents (each document is a string) and a keyword.
Returns list of the index values into the original list for all documents
containing the keyword.
Example:
doc_list = ['The Learn Python Challenge Casino', 'They bought a car, and a horse', 'Casinoville?']
word_search(doc_list, 'casino')
>>> [0]
"""
my_list = []
for doc in doc_list:
curr_doc = doc.lower()
try:
curr_index = curr_doc.index(keyword.lower())
my_list.append(curr_index)
except:
my_list.append(None)
return my_list
print(word_search(['The Learn Python Challenge Casino', 'They bought a car, and a horse', 'Casinoville?'], 'casino'))
output: [27, None, 0]
As you can see, in my code I am returning a List at the end of the function definition, as requested from the problem
A better approach to solve this would be to use the method contains(). An example of its usage can be found here.
So the algorithm would become:
list_to_return = []
counter = 0
for item in doc_list:
if item.contains(word):
list_to_return.append(counter)
counter += 1
return list_to_return
def word_search(doc_list, keyword):
res = []
sum = 0
for i in range(len(doc_list)-1):
if(doc_list[i] == keyword):
sum=sum+1
res.append(doc_list[i])
return sum, res

Python3: for-loop break and else (if statement)

Background information:
hey,
I want to do the following: I have a dictionary with IDs as keys and lists with various things as value. One of the items of the value is a string. I want to check, if a list contains this string. And I want to do it for all keys in my dictionary.
If the list contains the string, I want to print "String is valid"
If the list does not contain the string, I want to print "String is NOT valid"
So far, so good.
Furthermore, the lists I want to check depend on one console input of the user, which specifies, which list should be checked. The console input is "number".
My idea was to iterate over my dictionary and my list with a nested for-loop and compare, if the string(the item of the value) is equal to any list item. If it is, I want to break out of the loop. If the String is not found in the list, I want to execute the else-statement to print my "String is not valid" message.
Code snippet:
def validationHelper(myDict, myList):
for key in myDict:
for value in myDict[key][0]:
for item in myList:
if value==item:
validationHelper.true="String is valid"
break
else:
validationHelper.true="Warning: String is NOT valid"
def validation(anyList,helperfunc):
if anyList=="one":
return helperfunc(finalDict,myList1)
if anyList=="two":
return helperfunc(finalDict,myList2)
if anyList=="three":
return helperfunc(finalDict,myList3)
validation(number, validationHelper)
print(validationHelper.true)
Problem:
I am running this, but no matter if the string is in the list or not, I always get my printout for the else-statement. So, I guess I have an error in reasoning in my for-loop? Or maybe, I did not understand for-loops at all?! I have tried out different indentions with the else-statement, but couldnt solve my problem.
I would suggest you to change your function the following way (without changing the logic):
def validationHelper(myDict, myList):
for key in myDict:
for value in myDict[key][0]:
for item in myList:
if value==item:
return "String is valid" # Add here to exit
return "Warning: String is NOT valid" # will be returned inf nothing will be found in your 3 loops
def validation(anyList,helperfunc):
if anyList=="one":
return helperfunc(finalDict,myList1)
if anyList=="two":
return helperfunc(finalDict,myList2)
if anyList=="three":
return helperfunc(finalDict,myList3)
validation(number, validationHelper)
print(validationHelper)
This will help you to exit your 3 nested loops as it was mentioned in comments.
Because in the negative case on first wrong occurrence you don't need to check anything else.
Use return to break all of your loop. Having an else statement is not necessary if you don't have any if statement to begin with.
def validationHelper(myDict, myList):
for item in myList:
if item in myDict.values():
return ("String is valid")
return ("String is NOT valid")
def validation(anyList,helperfunc):
if anyList=="one":
return helperfunc(finalDict,myList1)
elif anyList=="two":
return helperfunc(finalDict,myList2)
elif anyList=="three":
return helperfunc(finalDict,myList3)
validation(number, validationHelper)
print(validationHelper.true)
Using elif instead of multiple if is a better practice. Be careful with indentions next time.
Also you might want to check .keys() and .values()
You can replace:
for key in myDict:
for value in myDict[key][0]:
with:
for value in myDict.values():
The other answers give a good explanation of how to break out of multiple loops. But you could also simplify your code by using Python's built-in functions and list comprehensions, like this:
def validationHelper(myDict, myList):
if any(v in myList for val in myDict.values() for v in val[0]):
validationHelper.true="String is valid"
else:
validationHelper.true="Warning: String is NOT valid"
def validation(anyList,helperfunc):
if anyList=="one":
return helperfunc(finalDict,myList1)
if anyList=="two":
return helperfunc(finalDict,myList2)
if anyList=="three":
return helperfunc(finalDict,myList3)
validation(number, validationHelper)
print(validationHelper.true)
This should be as efficient as your code, since any short circuits at the first match. And it may be a little more readable. (Note that multi-level list comprehensions go in the same order as regular for loops.)

Solving nested dict iteration by list comprehension or any other method

I have a dict containing another dict inside it
d1 = {'a':{'p':1, 'q':2, 'r':'abc'},
'b':{'p':5, 'q':6, 'r':["google", "pypi.org"]}
}
url1 = "https://google.com"
url2 = "https://abc.com"
Now what I want to do is run a check on values of r from both the dict values but I don't want any code redundancy.How is that possible ?
What I am doing right now is :-
for k, v in d1.iteritems():
if isinstance(v['r'], list):
for l in v['r']:
if url1.find(l):
..Do something..
else:
continue
else:
if url1.find(v['r'):
..Do Something same as above..
else:
continue
Now the problem arises with the same Do something repeated 2 times , Is there a way to solve redundancy with comprehension or by any other method , except function making and calling .
Edit-- The code is already inside a large function definition , so do provide other solutions than making another function and calling it.
You can convert the non-list items, ie. string in this case to a list and then simply iterate over that list. And you don't need that else: continue part:
for k, v in d1.iteritems():
value = v['r'] if isinstance(v['r'], list) else [v['r']]
for l in value:
if url1.find(l):
#Do something..
If you are dead serious about performance in python code and are willing to accept certain stylistic compromises, the following form will run just as fast as the manually inlined code, assuming you use pypy:
def inner():
pass
for k, v in d1.items():
if isinstance(v['r'], list):
for l in v['r']:
if url1.find(l):
inner()
else:
continue
else:
if url1.find(v['r']):
inner()
else:
continue
For a slightly more realistic example including non-empty inner and some timing code, please see this link.
Note that, just as you wrote, this version is significantly slower than the inlined one under CPython, which of course does no JIT inlining.
Freakish is right. Using a functions will be the best solution here. The overhead of using function calls will be negligible and it will probably be less than creating new lists and looping over lists of length one.
However, if you do want to avoid code duplication at all costs and avoid multiple function calls, you may want to consider rewriting your code to a generator function. This will yield the items you want to process one at a time.
def loop(d1):
for k, v in d1.iteritems():
if isinstance(v['r'], list):
for l in v['r']:
if url1.find(l):
yield l
else:
continue
else:
if url1.find(v['r']):
yield v['r']
else:
continue
for item in loop(d1):
print "do something"

Categories