使用python在树中找到匹配的节点

时间:2018-08-13 14:47:09

标签: python

我有一些数据,例如:

nodes = [
    {'name': 'N1', 'id': 1, 'color': 'grey', 'parent_id': None},
    {'name': 'N2', 'id': 2, 'color': 'grey', 'parent_id': 1},
    {'name': 'N3', 'id': 3, 'color': 'red', 'parent_id': 1},
    {'name': 'N4', 'id': 4, 'color': 'grey', 'parent_id': 1},
    {'name': 'N5', 'id': 5, 'color': 'red', 'parent_id': 1},
    {'name': 'N6', 'id': 6, 'color': 'grey', 'parent_id': 2},
    {'name': 'N7', 'id': 7, 'color': 'red', 'parent_id': 2},
    {'name': 'N8', 'id': 8, 'color': 'red', 'parent_id': 3},
    {'name': 'N9', 'id': 9, 'color': 'red', 'parent_id': 4},
    {'name': 'N10', 'id': 10, 'color': 'red', 'parent_id': 6},
    {'name': 'N11', 'id': 11, 'color': 'grey', 'parent_id': 6},
    {'name': 'N12', 'id': 12, 'color': 'grey', 'parent_id': 7},
    {'name': 'N13', 'id': 13, 'color': 'grey', 'parent_id': 7},
    {'name': 'N14', 'id': 14, 'color': 'red', 'parent_id': 9},
    {'name': 'N15', 'id': 15, 'color': 'grey', 'parent_id': 9},
    {'name': 'N16', 'id': 16, 'color': 'grey', 'parent_id': 9},
    {'name': 'N17', 'id': 16, 'color': 'red', 'parent_id': 15},
]

# data maybe in different sequence
import random
random.shuffle(nodes)

我为它画了一个图,如下:

tree nodes

我想找到颜色为“红色”的N1的子节点,当在路径中找到“红色”节点时,它应该停止,这意味着,当到达N3时,它将不再继续检查N8,同样,它将在N9处停止,而不检查N14,N15,N16和N17。

所以我期望的节点是:

expected_nodes = ['N3', 'N5', 'N7', 'N9', 'N10']

如何在python中做到这一点?

7 个答案:

答案 0 :(得分:2)

这是完成的方式。请注意,您下次可能要包括自己的尝试。

nodes = sorted(nodes, key=lambda x: x['id'])
blocked = []
res = []

for node in nodes:
    parent = node['parent_id']

    # If parent is blocked,
    if parent in blocked:
        blocked.append(node['id']) # Block the node

    # Otherwise, if node is red,
    elif node['color'] == 'red':
        blocked.append(node['id']) # Block the node
        res.append(node['name'])   # And add to the result

print(res) # ['N3', 'N5', 'N7', 'N9', 'N10']

答案 1 :(得分:2)

一种递归方法:

def r(nodes, p=None):
    o = []
    for n in nodes:
        if n['parent_id'] == p:
            if n['color'] == 'red':
                o.append(n['name'])
            else:
                o += r(nodes, n['id'])
    return o
print(r(nodes))

这将输出:

['N10', 'N7', 'N3', 'N9', 'N5']

答案 2 :(得分:2)

首先,我建议为您的节点实现一个特殊的类;否则,您可以使用库来实现诸如anytree之类的树。

这是我的尝试。它可能看起来很长,但是那是因为我想使其尽可能地通用。

编辑:如果您想继续使用字典格式(如@A Rather Long Name指出的那样),建议使用@A Rather Long Name's@blhsing's解决方案。我还在此答案的末尾添加了自己的方法。


使用自定义类

Node

在这里,我将如下定义类Node。有两个重要的说明:首先,我实现了Node,其中的信息指向孩子而不是父母。其次,每个节点的属性只是字符串name,字符串color和元组children

class Node:
    colors = ('grey', 'red')  # Allowed colors

    def __init__(self, name, color):
        self._name = str(name)
        self.color = str(color)
        self._children = ()  # Initialize as empty tuple

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, color_name):
        """Make sure it's one of the allowed colors; otherwise, default to grey"""
        if color_name in Node.colors:
            self._color = color_name
        else:
            self._color = 'grey'

    @property
    def children(self):
        return self._children

    @children.setter
    def children(self, children):
        """Make sure children are nodes"""
        if type(children) is tuple:
            children = tuple(filter(lambda item: isinstance(item, Node), children))
            self._children = children

    def __str__(self):
        """For 'printing' node to stdout"""
        return self._name

    def find_red_children(self):
        """Depth-first search of red children nodes"""
        result = []
        for child in self.children:
            if child.color == 'red':
                result.append(child)
            else:
                result.extend(child.find_red_children())  # Recursive call

        return result

树的初始化

我们按如下方式初始化树(而不是使用字典):

# Initialize all nodes
N1 = Node('N1', 'grey')
N2 = Node('N2', 'grey')
N3 = Node('N3', 'red')
N4 = Node('N4', 'grey')
N5 = Node('N5', 'red')
N6 = Node('N6', 'grey')
N7 = Node('N7', 'red')
N8 = Node('N8', 'red')
N9 = Node('N9', 'red')
N10 = Node('N10', 'red')
N11 = Node('N11', 'grey')
N12 = Node('N12', 'grey')
N13 = Node('N13', 'grey')
N14 = Node('N14', 'red')
N15 = Node('N15', 'red')
N16 = Node('N16', 'grey')



# Set connectivity
N1.children = (N2, N3, N4, N5)
N2.children = (N6, N7)
N3.children = (N8,)
N4.children = (N9,)
N6.children = (N10, N11)
N7.children = (N12, N13)
N9.children = (N14, N15, N16)


算法

该算法在Node类的find_red_children()方法中定义:

def find_red_children(self):
    """Depth-first search of red children nodes"""
    result = []
    for child in self.children:
        if child.color == 'red':
            result.append(child)
        else:
            result.extend(child.find_red_children())  # Recursive call

    return result

这是深度优先搜索(DFS)的实现(虽然无关紧要,但无论如何)。


测试运行

如果我们跑步

print(tuple(map(str, N1.find_red_children())))

输出为

('N10', 'N7', 'N3', 'N9', 'N5')

这就是我们想要的。请注意由于DFS的“从左到右”排序。



保持字典格式

如果您决定保留使用的词典格式,则可以使用以下功能:

def find_red(node_list, start_node_id=1):
    # Search node with given id:
    buffer = list(filter(lambda node: node['id'] == start_node_id, node_list))
    # Assert that there's one and only one node with that id:
    assert len(buffer) == 1, "Zero or more than one node with id %d" % start_node_id

    result = []
    while len(buffer) > 0:
        children = list(filter(lambda node: node['parent_id'] in [n['id'] for n in buffer], node_list))
        red = list(filter(lambda node: node['color'] == 'red', children))

        result.extend(red)
        buffer = [item for item in children if item not in red]

    return result

这是一种迭代的广度优先搜索方法。它不需要以前的排序,但是确实涉及很多迭代(对于filter和列表推导);我仍然认为,如果数据集很大,则不必进行排序将是一个更大的优势。

如果我们运行

print(list(map(lambda n: n['name'], find_red(nodes))))

我们得到['N3', 'N5', 'N7', 'N9', 'N10']


注意:您可以在if buffer[0]['color'] == 'red': return buffer[0]循环之前将while放在那里,这样,如果您从本身是红色的节点开始,它将返回该节点,但是这种边界情况是口味和预定用途有关。

答案 3 :(得分:0)

这可能有效:

>>> nodes = [
...     {'name': 'N1', 'id': 1, 'color': 'grey', 'parent_id': None},
...     {'name': 'N2', 'id': 2, 'color': 'grey', 'parent_id': 1},
...     {'name': 'N3', 'id': 3, 'color': 'red', 'parent_id': 1},
...     {'name': 'N4', 'id': 4, 'color': 'grey', 'parent_id': 1},
...     {'name': 'N5', 'id': 5, 'color': 'red', 'parent_id': 1},
...     {'name': 'N6', 'id': 6, 'color': 'grey', 'parent_id': 2},
...     {'name': 'N7', 'id': 7, 'color': 'red', 'parent_id': 2},
...     {'name': 'N8', 'id': 8, 'color': 'red', 'parent_id': 3},
...     {'name': 'N9', 'id': 9, 'color': 'red', 'parent_id': 4},
...     {'name': 'N10', 'id': 10, 'color': 'red', 'parent_id': 6},
...     {'name': 'N11', 'id': 11, 'color': 'grey', 'parent_id': 6},
...     {'name': 'N12', 'id': 12, 'color': 'grey', 'parent_id': 7},
...     {'name': 'N13', 'id': 13, 'color': 'grey', 'parent_id': 7},
...     {'name': 'N14', 'id': 14, 'color': 'red', 'parent_id': 9},
...     {'name': 'N15', 'id': 15, 'color': 'red', 'parent_id': 9},
...     {'name': 'N16', 'id': 16, 'color': 'grey', 'parent_id': 9},
... ]
>>> reds = [i for i in range(len(nodes)) if nodes[i]['color'] == 'red']
>>> reds
[2, 4, 6, 7, 8, 9, 13, 14]
>>> idx = [i for i in reds if nodes[nodes[i]['parent_id']-1]['color'] == 'grey']
>>> idx
[2, 4, 6, 8, 9]
>>> expected_nodes = [nodes[i]['name'] for i in idx]
>>> expected_nodes
['N3', 'N5', 'N7', 'N9', 'N10']

答案 4 :(得分:0)

您可以首先创建一个树来存储数据,然后实现自定义生成器功能以查找第一级红色节点:

class Tree:
  def __init__(self, args = [None, None, None]):
    self.id, self.name, self.color = args
    self.children = []
  def __contains__(self, _d:dict) -> bool:
    if not self.children and self.id != _d['parent_id']:
       return False
    return True if self.id == _d['parent_id'] else any(_d in i for i in self.children)
  def __iter__(self):
    if self.color == 'red':
      yield self.name
    else:
      for i in self.children:
        yield from i
  def insert_row(self, _row:dict) -> None:
    if _row['parent_id'] is None:
      self.__dict__.update(_row)
    else:
      if self.id == _row['parent_id']:
        self.children.append(Tree(args = [_row[i] for i in ['id', 'name', 'color']]))
      else:
         for i in self.children:
           if _row in i:
             i.insert_row(_row)
  def __repr__(self):
    return f'Tree({self.id}, {self.children})'

t = Tree()
for node in sorted(nodes, key=lambda x:x['id']):
  t.insert_row(node)

print([i for i in t])

输出:

['N10', 'N7', 'N3', 'N9', 'N5']

答案 5 :(得分:0)

另一种递归方法是:

nodeList = []

def adjacent(nodes, node):
    return [child for child in nodes if child['parent_id']==node['id'] and not child['id']==node['id']]


def reds(nodes, node):
    global nodeList
    if node['color']=='red':
        nodeList.append(node['name'])
        return
    else:
        children = adjacent(nodes, node)
        for child in children:
            reds(nodes, child)

reds(nodes, nodes[0])
print(nodeList)

输出:

['N10', 'N7', 'N3', 'N9', 'N5']

它从左到右搜索,直到找到红色的节点并停止。

答案 6 :(得分:0)

您可以尝试以下代码:

    def find_nodes(id_list,input_list):
        new_list = []
        for items in id_list:
            parent_list = list(filter(lambda x : x["parent_id"] == items["id"],input_list))     
            for dict_items in parent_list:            
                if(dict_items["color"] == "red"):
                    print(dict_items["name"])                    
                else:
                    new_list.append(dict_items)
        if(len(new_list) > 0):
            find_nodes(new_list,input_list)      


    if __name__ == '__main__' :
        input_list = []
        my_list = [
            {'name': 'N1', 'id': 1, 'color': 'grey', 'parent_id': None},
            {'name': 'N2', 'id': 2, 'color': 'grey', 'parent_id': 1},
            {'name': 'N3', 'id': 3, 'color': 'red', 'parent_id': 1},
            {'name': 'N4', 'id': 4, 'color': 'grey', 'parent_id': 1},
            {'name': 'N5', 'id': 5, 'color': 'red', 'parent_id': 1},
            {'name': 'N6', 'id': 6, 'color': 'grey', 'parent_id': 2},
            {'name': 'N7', 'id': 7, 'color': 'red', 'parent_id': 2},
            {'name': 'N8', 'id': 8, 'color': 'red', 'parent_id': 3},
            {'name': 'N9', 'id': 9, 'color': 'red', 'parent_id': 4},
            {'name': 'N10', 'id': 10, 'color': 'red', 'parent_id': 6},
            {'name': 'N11', 'id': 11, 'color': 'grey', 'parent_id': 6},
            {'name': 'N12', 'id': 12, 'color': 'grey', 'parent_id': 7},
            {'name': 'N13', 'id': 13, 'color': 'grey', 'parent_id': 7},
            {'name': 'N14', 'id': 14, 'color': 'red', 'parent_id': 9},
            {'name': 'N15', 'id': 15, 'color': 'red', 'parent_id': 9},
            {'name': 'N16', 'id': 16, 'color': 'grey', 'parent_id': 9},
        ]

        input_list = list(filter(lambda x : x["parent_id"] == None,my_list))
        find_nodes(input_list,my_list)