Reputation: 95
Alright, this is doing my head in. I have two dictionaries with object groups as shown below:
groups = {
'servers': ['unix_servers', 'windows_servers'],
'unix_servers': ['server_a', 'server_b', 'server_group'],
'windows_servers': ['server_c', 'server_d'],
'server_group': ['server_e', 'server_f']
}
hosts = {
'server_a': '10.0.0.1',
'server_b': '10.0.0.2',
'server_c': '10.0.0.3',
'server_d': '10.0.0.4',
'server_e': '10.0.0.5',
'server_f': '10.0.0.6'
}
The output I'm looking for is:
d3 = {
'servers': {
'unix_servers': {
'server_a': '10.0.0.1',
'server_b': '10.0.0.2',
'server_group': {
'server_e': '10.0.0.5',
'server_f': '10.0.0.6'
}
},
'windows_servers': {
'server_c': '10.0.0.3',
'server_d': '10.0.0.4'
}
}
}
The problem I'm having is that I don't know beforehand how much levels of recursion there are, as nested groups can theoretically go on infinitely. Additionally, I'm having trouble with determining which keys should be a top-level key in the combined dictionary.
I currently have the following:
def resolve(d1, d2):
for k, v in d1.items():
for i in v:
if i in d2.keys():
d3[k] = {i: d2[i]}
This returns:
{
"servers": {
"unix_servers": {
"server_a": "10.0.0.1",
"server_b": "10.0.0.2",
"server_group": {
"server_e": "10.0.0.5",
"server_f": "10.0.0.6"
}
},
"windows_servers": {
"server_c": "10.0.0.3",
"server_d": "10.0.0.4"
}
},
"unix_servers": {
"server_b": "10.0.0.2"
},
"windows_servers": {
"server_d": "10.0.0.4"
},
"server_group": {
"server_f": "10.0.0.6"
}
}
Which is close, but it's clearly missing recursion and doesn't handle nesting of keys. Mainly looking for pointers here, recursion logic doesn't click for me yet...
Upvotes: 7
Views: 254
Reputation: 2866
New to python I have been struggling though this but found a solution at last, it is quite long though.
groups = {
'servers': ['unix_servers', 'windows_servers'],
'unix_servers': ['server_a', 'server_b', 'server_group'],
'windows_servers': ['server_c', 'server_d'],
'server_group': ['server_e', 'server_f']
}
hosts = {
'server_a': '10.0.0.1',
'server_b': '10.0.0.2',
'server_c': '10.0.0.3',
'server_d': '10.0.0.4',
'server_e': '10.0.0.5',
'server_f': '10.0.0.6'
}
result = {}
parent = '';
levels = [];
def check(s,r):
if(r[s]==''): #check for each blank element in the result recursively
sublist = {}
for k in groups[s]: # check if the key exist in group if exist append children to result.
if k in hosts :
sublist[k] = hosts[k] # check if host exist in hosts for this key
else:
sublist[k]=''
if(key_exist(result, s)): # check if the key exist in result if exist append to result.
d = result
p = None
for key in levels:
p = d
d = d[key]
p[key] = sublist
del levels[:]
for x in sublist :
if(sublist[x] == ''):
check(x,p[key])
def resolve(r):
for s in r:
if(isinstance(r[s],dict)):
return resolve(r[s])
else:
check(s,r)
def key_exist(groups, key):
for s in groups:
if key in groups:
levels.append(key);
return True
else:
if(isinstance(groups[s],dict)):
levels.append(s);
return key_exist(groups[s], key)
return False
# Find the root or parent element
for k in groups.keys():
found = False
for j in groups.keys():
if k in groups[j]:
found = False
else:
found = True
if(found):
parent = k # root or parent element
# start making result with root element
s = {}
for k in groups[parent]:
s[k]='' # initialize child elements with blank
result[parent] = s
resolve(result)
print(result)
Upvotes: 0
Reputation: 71461
You can use simple recursion:
def build(val):
return {i:build(i) for i in groups[val]} if val in groups else hosts[val]
import json
print(json.dumps({'servers':build('servers')}, indent=4))
Output:
{
"servers": {
"unix_servers": {
"server_a": "10.0.0.1",
"server_b": "10.0.0.2",
"server_group": {
"server_e": "10.0.0.5",
"server_f": "10.0.0.6"
}
},
"windows_servers": {
"server_c": "10.0.0.3",
"server_d": "10.0.0.4"
}
}
}
Upvotes: 1
Reputation: 64855
Assuming you're ok with possibly having cross referencing data structure, you don't necessarily need to use recursion here.
from itertools import chain
group_dicts = {k: {} for k in groups}
for g in group_dicts:
for child_key in groups[g]:
child = group_dicts.get(child_key, hosts.get(child_key))
group_dicts[g][child_key] = child
# remove entries that are referenced at least once
not_top_levels = set(chain.from_iterable(groups.values()))
result = {g: group_dicts[g] for g in group_dicts if g not in not_top_levels}
Unlike other solutions, this will correctly handle cycles and infinitely recursive groups, as all the dict references are shared. When your groups
topologically describes a tree, this will work exactly the same as the recursive solution. However, if your groups
topologically describes a directed acyclic graph, this solution would share the dicts for all the nodes that appears more than once, while the recursive solution would copy and expand the duplicates out into a regular tree, this wouldn't really be an issue if you don't need to mutate the dicts. If your groups
topologically describes a graph with cycles, then this will create those cycles, while the recursive solution would fall due to infinite recursion.
Upvotes: 1
Reputation: 51
I guess it needs just two loops, and no more. Here, the groups dict
will be updated, more precisely, it'll lose some of its key-values.
groups = {k: {s: None for s in subs} for k, subs in groups.items()}
for k, subs in groups.items():
for subs_k, subs_v in subs.items():
if subs_v is not None:
continue
if subs_k in groups:
groups[k][subs_k] = groups[subs_k]
del groups[subs_k]
else:
groups[k][subs_k] = hosts[subs_k]
print(groups)
you'd probably like to re-write it using defaultdict
Upvotes: 0
Reputation: 23
d3={}
main={}
for i in groups['servers']:
if(i in groups):
d={}
for j in groups[i]:
if(j in groups):
dd={}
for k in groups[j]:
dd[k]=hosts[k]
d[j]=dd
else:
d[j]=hosts[j]
main[i]=d
d3['servers']=main
print(d3)
this will give :
{
'servers':
{
'unix_servers':
{
'server_group':
{
'server_f': '10.0.0.6',
'server_e': '10.0.0.5'
},
'server_b': '10.0.0.2',
'server_a': '10.0.0.1'
},
'windows_servers':
{
'server_c': '10.0.0.3',
'server_d': '10.0.0.4'
}
}
}
Upvotes: 0
Reputation: 59711
I think this does what you want:
def resolve(groups, hosts):
# Groups that have already been resolved
resolved_groups = {}
# Group names that are not root
non_root = set()
# Make dict with resolution of each group
result = {}
for name in groups:
result[name] = _resolve_rec(name, groups, hosts, resolved_groups, non_root)
# Remove groups that are not root
for name in groups:
if name in non_root:
del result[name]
return result
def _resolve_rec(name, groups, hosts, resolved_groups, non_root):
# If group has already been resolved finish
if name in resolved_groups:
return resolved_groups[name]
# If it is a host finish
if name in hosts:
return hosts[name]
# New group resolution
resolved = {}
for child in groups[name]:
# Resolve each child
resolved[child] = _resolve_rec(child, groups, hosts, resolved_groups, non_root)
# Mark child as non-root
non_root.add(child)
# Save to resolved groups
resolved_groups[name] = resolved
return resolved
With your example:
groups = {
'servers': ['unix_servers', 'windows_servers'],
'unix_servers': ['server_a', 'server_b', 'server_group'],
'windows_servers': ['server_c', 'server_d'],
'server_group': ['server_e', 'server_f']
}
hosts = {
'server_a': '10.0.0.1',
'server_b': '10.0.0.2',
'server_c': '10.0.0.3',
'server_d': '10.0.0.4',
'server_e': '10.0.0.5',
'server_f': '10.0.0.6'
}
d3 = {
'servers': {
'unix_servers': {
'server_a': '10.0.0.1',
'server_b': '10.0.0.2',
'server_group': {
'server_e': '10.0.0.5',
'server_f': '10.0.0.6'
}
},
'windows_servers': {
'server_c': '10.0.0.3',
'server_d': '10.0.0.4'
}
}
}
print(resolve(groups, hosts) == d3)
# True
Note this can fall into infinite recursion for malformed inputs, if you have for example group A
containing group B
but then group B
contains group A
.
Upvotes: 3