[英]Build nested tree-like dict from an array of dicts with children
我有一個從Web API檢索的dicts數組。 每個字典都有name
, description
,“父”和children
鍵。 children
鍵具有一系列值,因為它有價值。 為了清楚起見,這是一個虛擬的例子:
[
{'name': 'top_parent', 'description': None, 'parent': None,
'children': [{'name': 'child_one'},
{'name': 'child_two'}]},
{'name': 'child_one', 'description': None, 'parent': 'top_parent',
'children': []},
{'name': 'child_two', 'description': None, 'parent': 'top_parent',
'children': [{'name': 'grand_child'}]},
{'name': 'grand_child', 'description': None, 'parent': 'child_two',
'children': []}
]
數組中的每個項目。 一個項目可能是最頂層的母公司,因此在任何的不存在的children
陣列。 項目可以是孩子也可以是父母。 或者一個項目只能是一個孩子(沒有自己的孩子)。
所以,在樹形結構中,你會有這樣的東西:
top_parent
child_one
child_two
grand_child
在這個人為的簡化例子中, top_parent
是父母而不是孩子; child_one
是孩子,但不是父母; child_two
是父母和孩子; 和grand_child
是一個孩子,但不是父母。 這涵蓋了所有可能的狀態。
我想要的是能夠迭代dicts數組1次並生成一個正確表示樹結構的嵌套dict(但是,1次是不可能的,最有效的方式)。 所以,在這個例子中,我會得到一個看起來像這樣的字典:
{
'top_parent': {
'child_one': {},
'child_two': {
'grand_child': {}
}
}
}
嚴格來說,沒有孩子的項目沒有必要成為關鍵,但這是更可取的。
第四次編輯,顯示三個版本,清理了一下。 第一個版本自上而下工作,並按照您的要求返回None,但實質上是循環通過頂級數組3次。 下一個版本只循環一次,但返回空的dicts而不是None。
最終版本自下而上,非常干凈。 它可以使用單個循環返回空的dicts,或者使用其他循環返回None:
from collections import defaultdict
my_array = [
{'name': 'top_parent', 'description': None, 'parent': None,
'children': [{'name': 'child_one'},
{'name': 'child_two'}]},
{'name': 'child_one', 'description': None, 'parent': 'top_parent',
'children': []},
{'name': 'child_two', 'description': None, 'parent': 'top_parent',
'children': [{'name': 'grand_child'}]},
{'name': 'grand_child', 'description': None, 'parent': 'child_two',
'children': []}
]
def build_nest_None(my_array):
childmap = [(d['name'], set(x['name'] for x in d['children']) or None)
for d in my_array]
all_dicts = dict((name, kids and {}) for (name, kids) in childmap)
results = all_dicts.copy()
for (name, kids) in ((x, y) for x, y in childmap if y is not None):
all_dicts[name].update((kid, results.pop(kid)) for kid in kids)
return results
def build_nest_empty(my_array):
all_children = set()
all_dicts = defaultdict(dict)
for d in my_array:
children = set(x['name'] for x in d['children'])
all_dicts[d['name']].update((x, all_dicts[x]) for x in children)
all_children.update(children)
top_name, = set(all_dicts) - all_children
return {top_name: all_dicts[top_name]}
def build_bottom_up(my_array, use_None=False):
all_dicts = defaultdict(dict)
for d in my_array:
name = d['name']
all_dicts[d['parent']][name] = all_dicts[name]
if use_None:
for d in all_dicts.values():
for x, y in d.items():
if not y:
d[x] = None
return all_dicts[None]
print(build_nest_None(my_array))
print(build_nest_empty(my_array))
print(build_bottom_up(my_array, True))
print(build_bottom_up(my_array))
結果是:
{'top_parent': {'child_one': None, 'child_two': {'grand_child': None}}}
{'top_parent': {'child_one': {}, 'child_two': {'grand_child': {}}}}
{'top_parent': {'child_one': None, 'child_two': {'grand_child': None}}}
{'top_parent': {'child_one': {}, 'child_two': {'grand_child': {}}}}
您可以從名字到節點保持一個懶惰的映射,然后通過處理僅僅是重建層次parent
鏈接(我假設數據是正確的,所以如果A
被標記為父B
當且僅當 B
的兒童被列為A
) 。
nmap = {}
for n in nodes:
name = n["name"]
parent = n["parent"]
try:
# Was this node built before?
me = nmap[name]
except KeyError:
# No... create it now
if n["children"]:
nmap[name] = me = {}
else:
me = None
if parent:
try:
nmap[parent][name] = me
except KeyError:
# My parent will follow later
nmap[parent] = {name: me}
else:
root = me
輸入的children
屬性僅用於知道元素是否應該在其父元素中存儲為None
(因為沒有子元素),或者它是否應該是字典,因為它將在重建過程結束時生成子元素。 將沒有子節點的節點存儲為空字典可以通過避免需要這種特殊情況來簡化代碼。
使用collections.defaultdict
還可以簡化代碼以創建新節點
import collections
nmap = collections.defaultdict(dict)
for n in nodes:
name = n["name"]
parent = n["parent"]
me = nmap[name]
if parent:
nmap[parent][name] = me
else:
root = me
該算法是O(N)
假設是恆定時間字典訪問,並且只對輸入進行一次傳遞,並且對於名稱 - >節點映射需要O(N)
空間O(Nc)
原始nochildren->None
的空間要求為O(Nc)
nochildren->None
版本,其中Nc
是Nc
節點的節點數)。
我刺了它:
persons = [\
{'name': 'top_parent', 'description': None, 'parent': None,\
'children': [{'name': 'child_one'},\
{'name': 'child_two'}]},\
{'name': 'grand_child', 'description': None, 'parent': 'child_two',\
'children': []},\
{'name': 'child_two', 'description': None, 'parent': 'top_parent',\
'children': [{'name': 'grand_child'}]},\
{'name': 'child_one', 'description': None, 'parent': 'top_parent',\
'children': []},\
]
def findParent(name,parent,tree,found = False):
if tree == {}:
return False
if parent in tree:
tree[parent][name] = {}
return True
else:
for p in tree:
found = findParent(name,parent,tree[p],False) or found
return found
tree = {}
outOfOrder = []
for person in persons:
if person['parent'] == None:
tree[person['name']] = {}
else:
if not findParent(person['name'],person['parent'],tree):
outOfOrder.append(person)
for person in outOfOrder:
if not findParent(person['name'],person['parent'],tree):
print 'parent of ' + person['name'] + ' not found
print tree
結果是:
{'top_parent': {'child_two': {'grand_child': {}}, 'child_one': {}}}
它還會接收尚未添加其父級的所有子級,然后在最后對其進行協調。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.