我有一些数据,例如:
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)
我为它画了一个图,如下:
我想找到颜色为“红色”的N1的子节点,当在路径中找到“红色”节点时,它应该停止,这意味着,当到达N3时,它将不再继续检查N8,同样,它将在N9处停止,而不检查N14,N15,N16和N17。
所以我期望的节点是:
expected_nodes = ['N3', 'N5', 'N7', 'N9', 'N10']
如何在python中做到这一点?
答案 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)