How can I deal with recursion problem in such nested dictionary? - python

You've recieved a serialized JSON object from an API and have deserialized it using the standard library's json library. The object represents your geneology from a given ancestor downward. Assuming your name is Sally and your given ancestor is Janet, your geneology object would be as follows:
geneology_object = {
'husband': 'Craig',
'wife': 'Janet',
'children': {
'Chris': {
'husband': 'Chris',
'wife': 'Jesse',
'children': {
'Rebecca': {
'husband': 'Doug',
'wife': 'Rebecca',
}
}
},
'Wonda': {
'husband': 'Kevin',
'wife': 'Wonda',
'children': {
'Sally': {}
}
}
}
}
Write a function with the signature get_generations_down(geneology_object, search_name, generations=0) to recursively search for the number of generations between search_name and the eldest ancestor. If the name is not found, a NameNotFoundError should be raised by the recursive function.
Assuming the geneology object above, your function should behave as so:
get_generations_down(geneology_object, 'Chris') 1
get_generations_down(geneology_object, 'Sally') 2
My code is here,it doesn't work For 'Sally'
class NameNotFoundError(Exception):
pass
count=0
def get_generations_down(geneology_object, search_name, generations=0):
global count
for i in geneology_object:
if isinstance(geneology_object[i],dict):
if i=='children':
generations+=1
if search_name in geneology_object[i]:
count+=1
break
return
get_generations_down(geneology_object[i],search_name,generations)
elif i == search_name:
count+=1
break
elif geneology_object[i]== search_name:
count+=1
break
print(geneology_object)
if count==0:
raise NameNotFoundError
return generations
#raise NotImplementedError()
get_generations_down(geneology_object, 'Sally')

Your code doesn't seem to be a recursive solution.
Here's how I worked it out,
class NameNotFoundError (Exception):
pass
def get_generations_down(geneology_object, search_name, generations=0):
found=False
if ('husband' in geneology_object) and ('wife' in geneology_object) and (search_name == geneology_object['husband'] or search_name == geneology_object['wife']):
return True,generations
if 'children' in geneology_object:
children=(geneology_object['children'])
generations+=1
if search_name in children:
return True,generations
else:
for child in children:
if found:
break
found,generations=get_generations_down(geneology_object['children'][child],search_name,generations)
else:
generations-=1
return found,generations
result = get_generations_down(geneology_object, 'Sally')
if(result[0]):
print (result[1])
else:
raise NameNotFoundError()

Related

invoke dictionary fields dynamically by name

I have a function like this that allows to me to get the dictionary key like get_key(dictionary, 'path.to.key')
#staticmethod
def get_key(cont, key, default=None):
def get_key_inner(parent, keys, default):
if not keys:
return default
cur = keys.pop(0)
if cur not in parent:
return default
if not keys:
return parent[cur]
if isinstance(parent[cur], dict):
return get_key_inner(parent[cur], keys, default)
return default
return get_key_inner(cont, key.split('.'), default)
I want to implement some kind of object wrapper that would accept dictionary as a parameter and then would delegate the calls by name using the dictionary keys. Something like this
class Wrapper:
def __init__(self, dict_source):
self.source = dict_source
leon_dict = {
'name': 'Leon',
'job': {
'title': 'engineer'
}
}
leon = Wrapper(leon_dict)
assert 'engineer' == leon.job.title # the same as leon['job']['title']
Can I achieve that?
class Wrapper:
def __init__(self, dict_source):
self.source = dict_source
def __getattr__(self, key):
try:
data = self.source[key]
if isinstance(data, dict):
return Wrapper(data)
return data
except KeyError as e:
print(e)
raise AttributeError()
leon_dict = {
'name': 'Leon',
'job': {
'title': 'engineer'
}
}
leon = Wrapper(leon_dict)
print(leon.job.title)

Return values of certain key in a nested dictionary and form a hierarchy dictionary

I have a nested dictionary that looks like below:
d= {"key1":"A", "key2":"B", "score1":0.1, "score2":0.4, "depth":0,
"chain":[
{"key1":"A1", "key2":"B1", "score1":0.2, "score2":0.5, "depth":1,
"chain":[{"key1":"A11", "key2":"B11","score1":0.3, "score2":0.6, "depth":2},
{"key1":"A12", "key2":"B12","score1":0.5, "score2":0.7, "depth":2}]
},
{"key1":"A2", "key2":"B2","score1":0.1, "score2":0.2,"depth":1,
"chain":[{None, None, None, None, None},
{"key1":"A22", "key2":"B22","score1":0.1, "score2":0.5, "depth":2}]
}
]
}
I want to create a function that when I call fun(key1, d), it could return me a dictionary keeping the original hierarchy, but within each level, it will return the value of key1, and sum up the value of score1 and score2, like below:
{"A":0.5, "depth":0,
"chain":[
{"A1":0.7, "depth":1,
"chain":[{"A11":0.9,"depth":2},
{"A12":1.3, "depth":2}]
},
{"A2":0.3,"depth":1,
"chain":[None,
{"A22":0.6, "depth":2}]
}
]
}
How can I do this?
I have tried
def gen_dict_extract(key, input_dic):
return {input_dic[key]:input_dic["score1"]+input_dic["score2"],
"depth":input_dic["depth"],
"chain": gen_dict_extract(key,input_dic["chain"])}
There are two problems with the solution you've tried:
chain is not guaranteed to be present and
chain is a list of dictionaries and you are treating it as a single dictionary
Hopefully the following does what you want it to do:
def gen_dict_extract(key, input_dic):
rv = {
input_dic[key]: input_dic["score1"] + input_dic["score2"],
"depth": input_dic["depth"],
}
if "chain" in input_dic:
rv["chain"] = [gen_dict_extract(key, x) for x in input_dic["chain"]]
return rv
Since I have some None in the list of "chain", the following function worked in the end, which has some slight updates based on the solution #dvk provided:
def gen_dict_extract(key, input_dic):
rv = {
input_dic[key]: input_dic["score1"] + input_dic["score2"],
"depth": input_dic["depth"],
}
if "chain" in input_dic:
rv["chain"]=[]
for x in input_dic["chain"]:
if x is not None:
rv["chain"].insert(input_dic["chain"].index(x),gen_dict_extract(key, x))
return rv

How to filter multiple JSON data with Python?

I'm having some hard time filtering multiple json datas, I need to know the type of each data and if the type corresponds to a fruit then print the element's fields key, see python example comments for a better explanation.
Here's what the JSON looks like :
#json.items()
{
'type': 'apple',
'fields': {
'protein': '18g',
'glucide': '3%',
}
},
{
'type': 'banana',
'fields': {
'protein': '22g',
'glucide': '8%',
}
},
Here's what I tried to do :
for key, value in json.items(): #access json dict.
if key == 'type': #access 'type' key
if value == 'apple': #check the fruit
if key == 'fields': #ERROR !!! Now I need to access the 'fields' key datas of this same fruit. !!!
print('What a good fruit, be careful on quantity!')
print('more :' + value['protein'] + ', ' + value['glucid'])
if value == 'banana': #if not apple check for bananas
print('One banana each two days keeps you healthy !')
print('more:' + value['protein'] + ', ' + value['glucid'])
Is there a way I can achieve this ?
What you have seems to be a list of dicts.
You then check if keys type and fields exist in the dictionary before checking their value, like this:
for d in data: # d is a dict
if 'type' in d and 'fields' in d:
if d['type'] == 'apple':
... # some print statements
elif d['type'] == 'banana':
... # some more print statements
Based on your representation of the JSON, it appears that is actually a list, not a dictionary. So in order to iterate through it, you could try something like this:
for item in json:
fields = item['fields']
if item['type'] == 'banana':
print('Bananas have {} of protein and {} glucide'.format(fields['protein'], fields['glucide']))
elif item['type'] == 'apple':
print('Apples have {} of protein and {} glucide'.format(fields['protein'], fields['glucide']))

How do I return a dict from a function?

I have a small piece of code :
def extract_nodes():
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
print(i["label"])
print(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
I need to create a dict and return it to the calling function, i am not sure how do i create a dict and return from here. Also once returned how do i use the dictionary value
There may be more than one values for the key "id" and "label",so you should consider use list .
Here is my code
def extract_nodes():
labels = []
ids = []
results = {}
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
print(i["label"])
labels.append(i["label"])
print(i["id"])
ids.append(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
results['ip']=labels
results['id']=ids
return results
I hope it can work :)
You could use a generator, but I'm guessing you are new to python and this will be simpler:
def extract_nodes():
return_data = dict()
for node_datum in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(node_datum["label"])
return_data[node_datum["id"]] = { 'ip': node_datum["label"], 'id': node_datum["id"]}
print(node_datum["label"])
print(node_datum["id"])
#return { 'ip': node_datum["label"], 'id': node_datum["id"]} # i need to return these values
except Exception as err:
print err
pass
return return_data
As for using it,
node_data = extract_nodes()
for key, node_details in node_data.items():
print node_details['ip'], node_details['id']
def extract_nodes():
to_return_dict = dict()
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
to_return_dict[i['id']] = i['label']
print(i["label"])
print(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
return to_return_dict
This should do it.... Let me know if it works!
Edit:
As for how to use it:
id_label_dict = extract_nodes()
print(id_label_dict['ip']) # should print the label associated with 'ip'

Create a json tree from csv list in python

I'm trying to build a json hierarchy from a simple table in python.
The data comes in looking like the following:
id parent name
1 10 test-name-1
2 10 test-name-2
3 5 test-name-3
4 none test-name-4
5 10 test-name-5
6 none test-name-6
7 1 test-name-7
8 1 test-name-8
9 8 test-name-9
10 4 test-name-10
and I'm looking for an output like this:
{"$4":{"name":"test-name-4","children":{
"$10":{"name":"test-name-10","children":{
"$1":{"name":"test-name-1","children":{
"$7":{"name":"test-name-7","children":{}},
"$8":{"name":"test-name-8","children":{
"$9":{"name":"test-name-9","children":{}}}}}},
"$2":{"name":"test-name-2","children":{}},
"$5":{"name":"test-name-5","children":{
"$3":{"name":"test-name-3","children":{}}}}}}}},
"$6":{"name":"test-name-6","children":"test-name-6"}}
I have no idea how many "leaves" there will be or "roots", or what order the rows from the csv will come in. My question is, is there a way that I can recursively build a dictionary/list from a child node up to the parent? How can I produce a hierarchical tree from the "leaf" pieces of the tree in python?
Thanks for the help!
I have a solution based on 2 loops too (1 to cache, 1 to build), without JSON encoder, and that gives exactly the output you required:
>>> import re
>>> from collections import defaultdict
>>> parents = defaultdict(list)
>>> for i, line in enumerate(file_.split('\n')):
if i != 0 and line.strip():
id_, parent, name = re.findall(r'[\d\w-]+', line)
parents[parent].append((id_, name))
>>> parents
defaultdict(<type 'list'>, {'10': [('1', 'test-name-1'), ('2', 'test-name-2'), ('5', 'test-name-5')], 'none': [('4', 'test-name-4'), ('6', 'test-name-6')], '1': [('7', 'test-name-7'), ('8', 'test-name-8')], '5': [('3', 'test-name-3')], '4': [('10', 'test-name-10')], '8': [('9', 'test-name-9')]})
OK, now we have our cache, the recursive function easily builds the output we'd like:
>>> def build_tree(d, val):
return {'$' + id_: {'name': name, 'children': build_tree(d, id_)} for id_, name in d[val]}
We just have to call it on the dict built previously, with value 'none' which is the tree root:
>>> from pprint import pprint
>>> pprint(build_tree(parents, 'none'))
{'$4': {'children': {'$10': {'children': {'$1': {'children': {'$7': {'children': {},
'name': 'test-name-7'},
'$8': {'children': {'$9': {'children': {},
'name': 'test-name-9'}},
'name': 'test-name-8'}},
'name': 'test-name-1'},
'$2': {'children': {},
'name': 'test-name-2'},
'$5': {'children': {'$3': {'children': {},
'name': 'test-name-3'}},
'name': 'test-name-5'}},
'name': 'test-name-10'}},
'name': 'test-name-4'},
'$6': {'children': {}, 'name': 'test-name-6'}}
>>>
To assign all child nodes to its parent, you can do two passes over the list of nodes. The first pass adds each node to a UserDict. In the second pass the parent of each node is guaranteed to be in the UserDict so the node can be added to the children of its parent.
To serialize to JSON a JSONEncoder can be used.
#!/usr/bin/env python
import sys
import json
import UserDict
class Node(object):
def __init__(self, nid, parent, name):
self.nid = nid
self.parent = parent
self.children = []
self.name = name
class NodeDict(UserDict.UserDict):
def addNodes(self, nodes):
""" Add every node as a child to its parent by doing two passes."""
for i in (1, 2):
for node in nodes:
self.data[node.nid] = node
if node.parent in self.data.keys():
if node.parent != "none" and
node not in self.data[node.parent].children:
self.data[node.parent].children.append(node)
class NodeJSONEncoder(json.JSONEncoder):
def default(self, node):
if type(node) == Node:
return {"nid":node.nid, "name":node.name, "children":node.children}
raise TypeError("{} is not an instance of Node".format(node))
if __name__ == "__main__":
nodes = []
with open(sys.argv[1]) as f:
for row in f.readlines()[1:]:
nid, parent, name = row.split()
nodes.append(Node(nid, parent, name))
nodeDict = NodeDict()
nodeDict.addNodes(nodes)
rootNodes = [node for nid, node in nodeDict.items()
if node.parent == "none"]
for rootNode in rootNodes:
print NodeJSONEncoder().encode(rootNode)
Result:
{"name": "test-name-4", "nid": "4", "children":[
{"name": "test-name-10", "nid": "10", "children":[
{"name": "test-name-1", "nid": "1", "children":[
{"name": "test-name-7", "nid": "7", "children": []},
{"name": "test-name-8", "nid": "8", "children":[
{"name": "test-name-9", "nid": "9", "children": []}]}]},
{"name": "test-name-2", "nid": "2", "children": []},
{"name": "test-name-5", "nid": "5", "children":[
{"name": "test-name-3", "nid": "3", "children": []}]}]}]}
{"name": "test-name-6", "nid": "6", "children": []}
The answer given did not work for me in python 3.6 because Dict.Dict has been deprecated. So I made some changes to make it work and generalized it a little by letting user specify columns for child_id, parent_id and child name via command line. Please see below (I am just learning and am sure this could be improved, but it works for my purposes).
""" Converts a CSV file with Parent/Child Hierarchy to a hierarchical JSON file for front-end processing (javascript/DS)
USAGE: csv2json.py <somefile.csv> a b c (column nrs of a=child_id, b=parent-id, c=name(of child))
ROOT of hierarchy should contain child_id and parent_id = 'none' or blank. name must exist """
import sys
import json
import csv
#import UserDict
from collections import UserDict
class Node(object):
def __init__(self, child_id, parent_id, name):
self.child_id = child_id
self.parent_id = parent_id
self.children = []
self.name = name
class NodeDict(UserDict):
def addNodes(self, nodes):
""" Add every node as a child to its parent_id by doing two passes."""
for i in (1, 2):
for node in nodes:
self.data[node.child_id] = node
if node.parent_id in self.data.keys():
if (node.parent_id != "none" or node.parent_id != "") and node not in self.data[node.parent_id].children:
self.data[node.parent_id].children.append(node)
class NodeJSONEncoder(json.JSONEncoder):
def default(self, node):
if type(node) == Node:
return {"name":node.name, "children":node.children}
raise TypeError("{} is not an instance of Node".format(node))
if __name__ == "__main__":
nodes = []
with open(sys.argv[1], 'r') as f:
reader = csv.reader(f)
for row in reader:
if not row[int(sys.argv[4])] : #skip if no name/label exists
continue
child_id, parent_id, name = row[int(sys.argv[2])] , row[int(sys.argv[3])] , row[int(sys.argv[4])]
nodes.append(Node(child_id, parent_id, name))
nodeDict = NodeDict()
nodeDict.addNodes(nodes)
rootNodes = [node for child_id, node in nodeDict.items()
if (node.parent_id == "none" or node.parent_id == "")]
for rootNode in rootNodes:
print(NodeJSONEncoder().encode(rootNode))

Categories

Resources