Better method to Unpacks this Nested Dictionary? - python

I got a nested dictionary as follows and it will return the third key while inputting the first key in the dictionary
tree = {"Luke" : {"Darth Vader" : {"The Chancellor"}},
"Neal" : {"Les" : {"Joseph"}},
"George" : {"Fred" : {"Mickey"}},
"Robert" : {"Tim" : {"Michael"}},
"Juan" : {"Hunter" : {"Thompson"}}}
check_con = input("Enter your Name")
for fi_name,fi_second in tree.items():
if check_con in fi_name:
for fi_third,fi_fourth in fi_second.items():
print(fi_fourth)
I feel that its bit more steps, is there any other way to do it?
Regard

You can use dict.get method with a default value of empty dict to get the top level dict and then convert its values to a iter, use next to get the first value
>>> check_con = 'Neal'
>>> next(iter(tree.get(check_con, {}).values()), '')
{'Joseph'}
>>>
>>> check_con = 'xxx'
>>> next(iter(tree.get(check_con, {}).values()), '')
''
>>>

You can simply use a try-excep expression in order to find out whether your name exist in the dictionary or not. If it exist there you can then return all the values of the respective value:
def get_nested_item(tree, check_on):
try:
sub_dict = tree[check_on]
except KeyError:
print("No result")
return
else:
return sub_dict.values()
Also note that about checking the existence of your name in dictionary what you're ding here is a membership checking at following line:
if check_con in fi_name:
Which will not check for equality but checks if check_con appears within the dictionary keys. However if this is what you want you have to loop over your items and find the intended one. But also note that this may have multiple answers or in other words there may be multiple keys matching your criteria which contradicts with the whole purpose of using a dictionary.
Demo:
In [11]: get_nested_item(tree, "George")
Out[11]: dict_values([{'Mickey'}])
In [12]: get_nested_item(tree, "Luke")
Out[12]: dict_values([{'The Chancellor'}])
In [13]: get_nested_item(tree, "Sarah")
No result

this is a variant where i use next(iter(...)) in order to get the 'first' element of your dict and set (note that you innermost curly brackets in your tree are sets and not dicts):
def get(tree, name):
def first(seq):
return next(iter(seq))
if name in tree:
return first(first(tree[name].values()))
else:
return None
print(get(tree=tree, name='Juan')) # 'Thompson'
print(get(tree=tree, name='Jan')) # None
as both sets and dict_values (which is the type dict(...).values() returns) are not indexable (have no __getitem__ method) i turn them into an iterator using iter and get the first element using next.

Related

How to check if a tuple key is in a dict with O(1) time?

I'm trying to implement a hash table/hash map in Python.
Say I'm using tuples as keys like this:
hashTable = {}
node = [1, 2, 3]
print(hashTable[tuple(node)]) # throws an error
hashTable[tuple(node)] = True
print(hashTable[tuple(node)]) # prints TRUE
I want to check if elements exist in the hashTable before adding it. I have tried initializing the dictionary with all False values.
hashTable = {}
for i in range(1000):
hashTable[i] = False
So this creates a hash table of size 1000 with every slot set to FALSE. But if I try to check if a non-existent element is in the hashTable:
print(hashTable[tuple(node)])
I get the same error as before.
How does one go about doing this? I think this would work iterating through the dict with in but doesn't that defeat the whole purpose of using a hash table in the first place?
Accessing a key is similar to, but not necessarily the same as checking if it exists. To check if a key is in a dictionary, use dict.__contains__ via the in operator. To check if it is missing, use the not in operator:
key = tuple(node)
if key not in hashTable:
hashTable[key] = value
That being said, a totally valid way to check for containment can be by attempting access:
key = tuple(node)
try:
# attempt to use hashTable[key]
except KeyError:
# Do something with missing key
The advantage of doing it this way when both paths are needed is that you only need to access the dictionary once rather than twice.
Try to avoid calling tuple(node) over and over: it's not free. If you can, generate node as a tuple, do so. If not, perform the conversion once and use the converted value.
You can use the in operator to determine membership in a dictionary:
e.g.
if tuple(node) in hashTable:
x = hashTable[tuple(node)]
...
You can try to get the key and in case it is not in de dictionary yet, return a default value as may be None:
x = hashTable.get(node, default=None)

Comparing items through a tuple in Python

I am given an assignment when I am supposed to define a function that returns the second element of a tuple if the first element of a tuple matches with the argument of a function.
Specifically, let's say that I have a list of student registration numbers that goes by:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
And I have defined a function that is supposed to take in the argument of reg_num, such as "S12345", and return the name of the student in this case, "John". If the number does not match at all, I need to print "Not found" as a message. In essence, I understand that I need to sort through the larger tuple, and compare the first element [0] of each smaller tuple, then return the [1] entry of each smaller tuple. Here's what I have in mind:
def get_student_name(reg_num, particulars):
for i in records:
if reg_num == particulars[::1][0]:
return particulars[i][1]
else:
print("Not found")
I know I'm wrong, but I can't tell why. I'm not well acquainted with how to sort through a tuple. Can anyone offer some advice, especially in syntax? Thank you very much!
When you write for i in particulars, in each iteration i is an item of the collection and not an index. As such you cannot do particulars[i] (and there is no need - as you already have the item). In addition, remove the else statement so to not print for every item that doesn't match condition:
def get_student_name(reg_num, particulars):
for i in particulars:
if reg_num == i[0]:
return i[1]
print("Not found")
If you would want to iterate using indices you could do (but less nice):
for i in range(len(particulars)):
if reg_num == particulars[i][0]:
return particulars[i][1]
Another approach, provided to help learn new tricks for manipulating python data structures:
You can turn you tuple of tuples:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
into a dictionary:
>>> pdict = dict(particulars)
>>> pdict
{'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
You can look up the value by supplying the key:
>>> r = 'S23456'
>>> dict(pdict)[r]
'Max'
The function:
def get_student_name(reg, s_data):
try:
return dict(s_data)[reg]
except:
return "Not Found"
The use of try ... except will catch errors and just return Not Found in the case where the reg is not in the tuple in the first place. It will also catch of the supplied tuple is not a series of PAIRS, and thus cannot be converted the way you expect.
You can read more about exceptions: the basics and the docs to learn how to respond differently to different types of error.
for loops in python
Gilad Green already answered your question with a way to fix your code and a quick explanation on for loops.
Here are five loops that do more or less the same thing; I invite you to try them out.
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
for t in particulars:
print("{} {}".format(t[0], t[1]))
for i in range(len(particulars)):
print("{}: {} {}".format(i, particulars[i][0], particulars[i][1]))
for i, t in enumerate(particulars):
print("{}: {} {}".format(i, t[0], t[1]))
for reg_value, student_name in particulars:
print("{} {}".format(reg_value, student_name))
for i, (reg_value, student_name) in enumerate(particulars):
print("{}: {} {}".format(i, reg_value, student_name))
Using dictionaries instead of lists
Most importantly, I would like to add that using an unsorted list to store your student records is not the most efficient way.
If you sort the list and maintain it in sorted order, then you can use binary search to search for reg_num much faster than browsing the list one item at a time. Think of this: when you need to look up a word in a dictionary, do you read all words one by one, starting by "aah", "aback", "abaft", "abandon", etc.? No; first, you open the dictionary somewhere in the middle; you compare the words on that page with your word; then you open it again to another page; compare again; every time you do that, the number of candidate pages diminishes greatly, and so you can find your word among 300,000 other words in a very small time.
Instead of using a sorted list with binary search, you could use another data structure, for instance a binary search tree or a hash table.
But, wait! Python already does that very easily!
There is a data structure in python called a dictionary. See the documentation on dictionaries. This structure is perfectly adapted to most situations where you have keys associated to values. Here the key is the reg_number, and the value is the student name.
You can define a dictionary directly:
particulars = {'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
Or you can convert your list of tuples to a dictionary:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
particulars_as_dict = dict(particulars)
Then you can check if an reg_number is in the dictionary, with they keyword in; you can return the student name using square brackets or with the method get:
>>> particulars = {'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
>>> 'S23456' in particulars
True
>>> 'S98765' in particulars
False
>>>
>>> particulars['S23456']
'Max'
>>> particulars.get('S23456')
'Max'
>>> particulars.get('S23456', 'not found')
'Max'
>>>
>>> particulars['S98765']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'S98765'
>>> particulars.get('S98765')
None
>>> particulars.get('S98765', 'not found')
'not found'

Optimize a dictionary key conditional

I would like to optimize this piece of code. I'm sure there is a way to write it in a single line:
if 'value' in dictionary:
x = paas_server['support']
else:
x = []
use dictionary get() method as:
x = dictionary.get('support', [])
if support is not a key in the dictionary, it returns second method's argument, here, an empty list.

Python string manipulation without comparison

I am trying to solve this problem: I have some symbols:
list =["RBS-UK", "GOOG-US"]
Now I have to transform all the region occurrences of "UK" to "GB". I could have done this easily:
new_list =[]
for symbol in list :
temp_list=symbol.split("-")
if temp_list[1]=="UK":
temp_list[1]="GB"
new_list.append("-".join(temp_list))
But can I do this without the equality comparision?
I am looking for something along the lines of:
some_dict={}
new_list =[]
for symbol in list :
temp_list=symbol.split("-")
temp_list[1]=some_dict(temp_list[1]) # So this dict returns GB if its UK else returns same value as it is
new_list.append("-".join(temp_list))
Is it possible to do this, or are there any other solutions?
Yeah! sure
ls =['RBS-UK','GOOG-US']
map(lambda x: x.replace('-UK', '-GB'), ls)
You are looking for a lookup, for which a dictionary will work:
translations = {'UK':'GB'} # and so on
for symbol in lst:
country_code = symbol.split('-')[1]
translated = translations.get(country_code,country_code)
new_list.append('{}-{}'.format(symbol.split('-')[0],translated))
The key line is:
translated = translations.get(country_code,country_code)
Dictionary have a method get() which will return None if the key is not found. We use this to avoid raising KeyError. get() takes an optional second parameter for a value to return other than None if the key is not found.
In the snippet above, we pass the country code to get(), and ask it to return the same country code if there isn't a translation available, otherwise return the translation.
The second line uses string formatting to reconstruct the original symbol with the translated code and appends it to your list.
You don't actually have to redefine the offset. You can simply replace the string:
for symbol in list:
symbol = symbol.replace('-UK','-GB')
If the string is encountered it will be replaced, otherwise it is left alone entirely.
If you really want to use a dict, you could use the dict.get method, which accepts a default argument used when the key isn't found, and so some_dict.get(x,x) means "return the value associated with x if it exists, otherwise return x":
>>> some_dict = {"UK": "GB"}
>>> country = "UK"
>>> some_dict.get(country, country)
'GB'
>>> country = "CA"
>>> some_dict.get(country, country)
'CA'
You can use the sub function from the re module useful for regular expression operations.
Here is a one-liner which produces the list you want:
import re
newlist = [re.sub('UK','GB', symbol) for symbol in list]

How to retrieve from python dict where key is only partially known?

I have a dict that has string-type keys whose exact values I can't know (because they're generated dynamically elsewhere). However, I know that that the key I want contains a particular substring, and that a single key with this substring is definitely in the dict.
What's the best, or "most pythonic" way to retrieve the value for this key?
I thought of two strategies, but both irk me:
for k,v in some_dict.items():
if 'substring' in k:
value = v
break
-- OR --
value = [v for (k,v) in some_dict.items() if 'substring' in k][0]
The first method is bulky and somewhat ugly, while the second is cleaner, but the extra step of indexing into the list comprehension (the [0]) irks me. Is there a better way to express the second version, or a more concise way to write the first?
There is an option to write the second version with the performance attributes of the first one.
Use a generator expression instead of list comprehension:
value = next(v for (k,v) in some_dict.iteritems() if 'substring' in k)
The expression inside the parenthesis will return an iterator which you will then ask to provide the next, i.e. first element. No further elements are processed.
How about this:
value = (v for (k,v) in some_dict.iteritems() if 'substring' in k).next()
It will stop immediately when it finds the first match.
But it still has O(n) complexity, where n is the number of key-value pairs. You need something like a suffix list or a suffix tree to speed up searching.
If there are many keys but the string is easy to reconstruct from the substring, then it can be faster reconstructing it. e.g. often you know the start of the key but not the datestamp that has been appended on. (so you may only have to try 365 dates rather than iterate through millions of keys for example).
It's unlikely to be the case but I thought I would suggest it anyway.
e.g.
>>> names={'bob_k':32,'james_r':443,'sarah_p':12}
>>> firstname='james' #you know the substring james because you have a list of firstnames
>>> for c in "abcdefghijklmnopqrstuvwxyz":
... name="%s_%s"%(firstname,c)
... if name in names:
... print name
...
james_r
class MyDict(dict):
def __init__(self, *kwargs):
dict.__init__(self, *kwargs)
def __getitem__(self,x):
return next(v for (k,v) in self.iteritems() if x in k)
# Defining several dicos ----------------------------------------------------
some_dict = {'abc4589':4578,'abc7812':798,'kjuy45763':1002}
another_dict = {'boumboum14':'WSZE x478',
'tagada4783':'ocean11',
'maracuna102455':None}
still_another = {12:'jfg',45:'klsjgf'}
# Selecting the dicos whose __getitem__ method will be changed -------------
name,obj = None,None
selected_dicos = [ (name,obj) for (name,obj) in globals().iteritems()
if type(obj)==dict
and all(type(x)==str for x in obj.iterkeys())]
print 'names of selected_dicos ==',[ name for (name,obj) in selected_dicos]
# Transforming the selected dicos in instances of class MyDict -----------
for k,v in selected_dicos:
globals()[k] = MyDict(v)
# Exemple of getting a value ---------------------------------------------
print "some_dict['7812'] ==",some_dict['7812']
result
names of selected_dicos == ['another_dict', 'some_dict']
some_dict['7812'] == 798
I prefer the first version, although I'd use some_dict.iteritems() (if you're on Python 2) because then you don't have to build an entire list of all the items beforehand. Instead you iterate through the dict and break as soon as you're done.
On Python 3, some_dict.items(2) already results in a dictionary view, so that's already a suitable iterator.

Categories