Python:从父子值列表创建嵌套字典

5

以下是输入内容:

list_child_parent= [
    #first value is child, second is parent
    (0, 1),
    (1, 3),
    (8, 7),
    (3, 6),
    (4, 3),
    (5, 3)
]

输出需要使用这些值创建一个嵌套的字典树。树形结构永远不会超过6层。
例如:
output_dict = {
    6: {3: {1: {0: {}}, 4: {}, 5: {}}}, 7: {8: {}}
}

我花了两天时间尝试完成这个任务。我尝试编写函数来查找树中的键位,并在其后添加新键位,但我无法编写能够继续超过3个级别的代码。这令人困惑,我感觉可能有一个标准库可以做到这一点。
我的经验水平比较低。

这些配对没有任何模式。你应该假设父子层级列表可以是任意顺序。 - Dan Safee
没有其他可能的结果。输入列表始终相同,但顺序可能不同。 - Dan Safee
如果您的代码不像您期望的那样工作,将其发布出来是一个好主意。解决问题的一种方法可能是创建每个元素的子元素字典(仅包含数字),并跟踪尚未找到父元素的元素。然后,您可以从没有父元素的元素开始构建嵌套字典。 - Amy Teegarden
我的代码很难阅读,因为输入列表来自数据库,并涉及许多类和子类。我会尝试修改它,以便您可以看到我的方法,但它正是我上面描述的那样。 - Dan Safee
1
你的树不应该是{6: {3: {1: {0: {}}, 4: {}, 5: {}}}, 7: {8: {}}}吗?你在元组中展示了3有子节点1、5、4,但输出结果似乎让人以为1有子节点5、4、0。 - Matt
显示剩余9条评论
3个回答

4

这段代码可能不太美观,也不一定符合Pythonic风格,但它可以帮助你入门:

#!/usr/bin/env python3

def make_map(list_child_parent):
    has_parent = set()
    all_items = {}
    for child, parent in list_child_parent:
        if parent not in all_items:
            all_items[parent] = {}
        if child not in all_items:
            all_items[child] = {}
        all_items[parent][child] = all_items[child]
        has_parent.add(child)

    result = {}
    for key, value in all_items.items():
        if key not in has_parent:
            result[key] = value
    return result

if __name__ == '__main__':
    list_child_parent = [
        #first value is child, second is parent
        (0, 1),
        (1, 3),
        (8, 7),
        (3, 6),
        (4, 3),
        (5, 3)
    ]

    actual = make_map(list_child_parent)

    expected = {
        6: {
            3: {
                1: {
                    0: {}
                },
                4: {},
                5: {}
            }
        },
        7: {
            8: {}
        }
    }
    print('OK' if expected == actual else 'FAIL')

1
这个解决方案非常完美。它将我原本无法工作的45行代码压缩成了一个简单易懂的解决方案。我以前从未见过set()以这种方式使用,而且在阅读Python文档后,你的用法并不明显。需要注意的是,一旦树被构建,它就会保存在缓存中,因此性能不是问题。但是,有几千个父/子关系,并且随着添加新数据,定期需要重建树。这可能会作为一个cron运行,用于我正在构建的flask网站。 - Dan Safee
你能帮忙反转嘛,也就是从同一个嵌套字典中生成父子对吗? - user3156040
@user3156040 答案可能无法适应评论区。您可以创建一个新问题然后在这里发布链接吗? - Rusty Shackleford

2

这段代码将会把给定格式的树转换成一个树形字典。虽然看起来有点冗长,但它有助于跟踪发生了什么。从性能方面来说,它表现得相当不错。

LIST_CHILD_PARENTS = [                                                     
#first value is child, second is parent                                    
(0, 1),                                                                    
(1, 3),                                                                    
(8, 7),                                                                    
(3, 6),                                                                    
(4, 3),                                                                    
(5, 3)                                                                     
]                                                                          


class Node(object):                                                        
    def __init__(self, value):                    
        self.value = value
        # List of references to Node()'s.                                                
        self.child = []              
        # Reference to parent Node()                                                                           
        self.parent = None                                               
    def set_parent(self, parent):                                          
        self.parent = parent                                               
    def set_child(self, child):                                            
        self.child.append(child)                                           


def get_a_root(items):                                                     
    """Find a root node from items.                                        

    Grab some node and follow the parent pointers to a root.               
    """                                                                    
    cur_key = list(items.keys())[0]                                              
    while items[cur_key].parent is not None:                               
        cur_key = items[cur_key].parent.value                              
    parent = items[cur_key]                                                
    return parent                                                          

def extract_tree(items, root):                                             
    """Remove the tree from root in items.                                 
    """                                                                    
    cur_key = root.value                                                   
    this_node = items[cur_key]                                             
    if len(this_node.child) == 0:                                          
        items.pop(cur_key)                                                 
        return                                                             
    else:                                                                  
        for child in this_node.child:                                      
            extract_tree(items, child)                                     
        items.pop(cur_key)                                                  

def insert_from_root(tree, root):                                          
    """Insert the dictionary items from a tree.                            
    """                                                                    
    current = root                                                         
    if len(current.child) == 0:                                            
        tree[current.value] = {}                                           
        return                                                             
    else:                                                                  
        table = {}                                                         
        for child in current.child:                                        
            insert_from_root(table, child)                                 
        tree[current.value] = table                                                                                                

def build_graphs():                                                        
    """Map all input graphs into Node(object)'s.           

    Return: A hash table by value: Node(value, child, parent)              
    """                                                                    
    items = {}                                                             
    for child, parent in LIST_CHILD_PARENTS:                               
        if not child in items:                                       
            c_n = Node(child)  
            items[child] = c_n                 
        else:                                                              
            c_n = items[child]                                             
        if not parent in items:                                      
            p_n = Node(parent) 
            items[parent] = p_n                    
        else:                                                              
            p_n = items[parent]                                            
        p_n.set_child(c_n)                                                 
        c_n.set_parent(p_n)                                                                                       
    return items                                                           

def run_dict_builder():                                                    
    """Map the graphs from input and map into a dict.                                  

    Sequence:                                                              
        1- Map all graphs from input trees: list(tuple)             
        2- For each root node:                                             
            2A - Get a root node.                                      
            2B - Extract tree under this root from graphs list.                  
            2C - Insert the tree from this root into dict.                      
        3- Return the Dictionary Tree structure.                                                     
    """                                                                    
    graphs = build_graphs()                                                

    h_table = {}                                                           
    while len(graphs) > 0:                                                 
        root = get_a_root(graphs)                                          
        extract_tree(graphs, root)                                
        insert_from_root(h_table, root)                          
    return h_table                                                         

print(run_dict_builder())

Matt,感谢您提供了一个非常好的解决方案。这个方法很有效,但是我需要在Python 3环境中使用它(应该在最开始说明 - 抱歉!)。我该如何更改第67行的items.has_key以使其与Python 3兼容?AttributeError: 'dict' object has no attribute 'has_key' - Dan Safee
@DanSafee 你可以使用:if parent in items: 来代替 if key in dict: - Matt

0
尽管这个问题很久以前就有了答案,但我想提供另一个解决方案,对我来说似乎更加简洁。我主要将其用于将父母和子项列表一起压缩的情况。例如:
parents = [None, 1, 2, 3, 3, 2, 6, 6, 1, 9, 10, 10, 9, 13, 13]
children = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

# {1: {2: {3: {4: {}, 5: {}}, 6: {7: {}, 8: {}}}, 9: {10: {11: {}, 12: {}}, 13: {14: {}, 15: {}}}}}

实现:

def create_tree(node_map, root=None):
""" Given a list of tuples (child, parent) return the nested dictionary representation.
"""

def traverse(parent, node_map, seen):
    children = {}
    for edge in node_map:
        if edge[1] == parent and edge[0] not in seen:
            seen.add(edge[0])
            children[edge[0]] = traverse(edge[0], node_map, seen)
    return children

return traverse(root, node_map, {root})

使用方法:

排除根节点的示例。

我们希望将根节点设置为“a”,但由于根节点应被指定为没有父节点(None),因此被排除在外。

parents = ["a", "b", "c", "a", "b", "e", "e"]
children = ["b", "c", "d", "e", "f", "g", "h"]
edges = list(zip(children, parents))
test_tree = create_tree(edges, "a")
print(test_tree)
# result {'b': {'c': {'d': {}}, 'f': {}}, 'e': {'g': {}, 'h': {}}}

将根节点添加到没有父节点的设计中,将该节点指定为根节点。

parents = [None, "a", "b", "c", "a", "b", "e", "e"]
children = ["a", "b", "c", "d", "e", "f", "g", "h"]
edges = list(zip(children, parents))
test_tree = create_tree(edges, None)
print(test_tree)
# result: {'a': {'b': {'c': {'d': {}}, 'f': {}}, 'e': {'g': {}, 'h': {}}}}

多个树可以通过有多个根节点来表示。

parents = [None, "a", "b", "c", None, "b", "e", "e"]
children = ["a", "b", "c", "d", "e", "f", "g", "h"]
edges = list(zip(children, parents))
test_tree = create_tree(edges, None)
print(test_tree)
# result: {'a': {'b': {'c': {'d': {}}, 'f': {}}}, 'e': {'g': {}, 'h': {}}}

为了从原始帖子中获得所需的输出,我们需要找到并指定根目录。
edges = [
    #first value is child, second is parent
    (0, 1),
    (1, 3),
    (8, 7),
    (3, 6),
    (4, 3),
    (5, 3)
]
roots = {edge[1] for edge in edges} - {edge[0] for edge in edges}
edges += [(root, None) for root in roots]
test_tree = create_tree(edges, None)
print(test_tree)
# result: {6: {3: {1: {0: {}}, 4: {}, 5: {}}}, 7: {8: {}}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接