Python中带有生成器的DFS算法 背景:
我在做一个项目,我需要为文本处理编写一些规则。在这个项目上工作了几天并实施了一些规则之后,我意识到我需要确定规则的顺序。没问题,我们有拓扑排序帮助。但后来我意识到我不能期望图表总是满的。所以我提出了这个想法,给定一个规则和一组依赖项(或一个依赖项),我需要检查依赖项的依赖项。听起来熟悉吗?对此主题与图形的深度优先搜索非常相似。Python中带有生成器的DFS算法 背景:,python,algorithm,generator,depth-first-search,Python,Algorithm,Generator,Depth First Search,我在做一个项目,我需要为文本处理编写一些规则。在这个项目上工作了几天并实施了一些规则之后,我意识到我需要确定规则的顺序。没问题,我们有拓扑排序帮助。但后来我意识到我不能期望图表总是满的。所以我提出了这个想法,给定一个规则和一组依赖项(或一个依赖项),我需要检查依赖项的依赖项。听起来熟悉吗?对此主题与图形的深度优先搜索非常相似。 我不是数学家,也没有学习C.S。因此,图论对我来说是一个新的领域。尽管如此,我还是实现了一些有效的东西(见下文)(我怀疑效率低下) 守则: 这是我的搜索和屈服算法。如果在
我不是数学家,也没有学习C.S。因此,图论对我来说是一个新的领域。尽管如此,我还是实现了一些有效的东西(见下文)(我怀疑效率低下) 守则: 这是我的搜索和屈服算法。如果在下面的示例中运行它,您将看到它多次访问某些节点。因此,推测的效率低下。
一句关于输入的话。我编写的规则基本上是python类,它们有一个class属性
dependens
。有人批评我没有使用inspect.getmro
——但这会使事情变得非常复杂,因为类需要相互继承()
好的,现在您已经开始阅读代码,下面是一些您可以测试的输入:
demo_class_content ="""
class A(object):
depends = ('B')
def __str__(self):
return self.__class__.__name__
class B(object):
depends = ('C','F')
def __str__(self):
return self.__class__.__name__
class C(object):
depends = ('D', 'E')
def __str__(self):
return self.__class__.__name__
class D(object):
depends = None
def __str__(self):
return self.__class__.__name__
class F(object):
depends = ('E')
def __str__(self):
return self.__class__.__name__
class E(object):
depends = None
def __str__(self):
return self.__class__.__name__
"""
with open('demo_classes.py', 'w') as clsdemo:
clsdemo.write(demo_class_content)
import demo_classes as rules
rule_start={'A': ('B')}
def _yield_name_dep(rules_deps):
# yield all rules by their named and dependencies
for rule, dep in rules_deps.items():
if not dep:
yield rule, dep
continue
else:
yield rule, dep
for ii in dep:
i = getattr(rules, ii)
instance = i()
if instance.depends:
new_dep={str(instance): instance.depends}
for dep in _yield_name_dep(new_dep):
yield dep
else:
yield str(instance), instance.depends
if __name__ == '__main__':
# this is yielding nodes visited multiple times,
# list(_yield_name_dep(rule_start))
# hence, my work around was to use set() ...
rule_dependencies = list(set(_yield_name_dep(rule_start)))
print rule_dependencies
问题是:
- 我尝试对我的工作进行分类,我认为我所做的与DFS类似。你真的能这样分类吗
- 如何改进此功能以跳过已访问的节点,并且仍然使用生成器
>>> print list(_yield_name_dep(rule_wd))
[('A', 'B'), ('B', ('C', 'F')), ('C', ('D', 'E')), ('D', None), ('E', None), ('F', 'E'), ('E', None)]
>>> print list(set(_yield_name_dep(rule_wd)))
[('B', ('C', 'F')), ('E', None), ('D', None), ('F', 'E'), ('C', ('D', 'E')), ('A', 'B')]
同时,虽然我提出了一个更好的解决方案,但上面的问题仍然存在。因此,请随意批评我的解决方案:
visited = []
def _yield_name_dep_wvisited(rules_deps, visited):
# yield all rules by their name and dependencies
for rule, dep in rules_deps.items():
if not dep and rule not in visited:
yield rule, dep
visited.append(rule)
continue
elif rule not in visited:
yield rule, dep
visited.append(rule)
for ii in dep:
i = getattr(grules, ii)
instance = i()
if instance.depends:
new_dep={str(instance): instance.depends}
for dep in _yield_name_dep_wvisited(new_dep, visited):
if dep not in visited:
yield dep
elif str(instance) not in visited:
visited.append(str(instance))
yield str(instance), instance.depends
上面的输出是:
>>>list(_yield_name_dep_wvisited(rule_wd, visited))
[('A', 'B'), ('B', ('C', 'F')), ('C', ('D', 'E')), ('D', None), ('E', None), ('F', 'E')]
正如您现在看到的,节点E只被访问了一次。根据Gareth和Stackoverflow的其他用户的反馈,下面是我的想法。它更清楚,也更一般:
def _dfs(start_nodes, rules, visited):
"""
Depth First Search
start_nodes - Dictionary of Rule with dependencies (as Tuples):
start_nodes = {'A': ('B','C')}
rules - Dictionary of Rules with dependencies (as Tuples):
e.g.
rules = {'A':('B','C'), 'B':('D','E'), 'C':('E','F'),
'D':(), 'E':(), 'F':()}
The above rules describe the following DAG:
A
/ \
B C
/ \ / \
D E F
usage:
>>> rules = {'A':('B','C'), 'B':('D','E'), 'C':('E','F'),
'D':(), 'E':(), 'F':()}
>>> visited = []
>>> list(_dfs({'A': ('B','C')}, rules, visited))
[('A', ('B', 'C')), ('B', ('D', 'E')), ('D', ()), ('E', ()),
('C', ('E', 'F')), ('F', ())]
"""
for rule, dep in start_nodes.items():
if rule not in visited:
yield rule, dep
visited.append(rule)
for ii in dep:
new_dep={ ii : rules[ii]}
for dep in _dfs(new_dep, rules, visited):
if dep not in visited:
yield dep
这里有另一种方法可以在不复制访问节点的情况下执行广度优先搜索
import pylab
import networkx as nx
G = nx.DiGraph()
G.add_nodes_from([x for x in 'ABCDEF'])
G.nodes()
返回['A','C','B','E','D','F']
G.add_edge('A','B')
G.add_edge('A','C')
G.add_edge('B','D')
G.add_edge('B','E')
G.add_edge('C','E')
G.add_edge('C','F')
下面是如何在不复制节点的情况下遍历树
nx.traversal.dfs_successors(G)
返回{'A':['C','B'],'B':['D'],'C':['E','F']}
你可以画出图表
nx.draw(G,node_size=1000)
为什么不将类本身存储在
中取决于而不是类的名称呢?@Eric,首先是因为我不知道怎么做。第二,这里的课程只是为了演示。该代码适用于配置文件中定义的一些属性,我想要一个自包含的示例。你能说明你的意思吗?依赖=(E,F)
,不带引号。请注意,您需要在文件中更早地定义依赖的对象,而不是依赖于它们的对象,但这对于可读性来说可能是一个好主意。我相信这个问题更适合于任何情况,我相信如果您总是使用元组表示依赖
,您可以简化代码。我的意思是,如果有dependens=None
,dependens='A'
和dependens=('A','B')
,你必须明确地对待每一种情况。您可以简单地使用dependens=()
,dependens=('A',)
和dependens=('A','B')
,并使用更统一的代码。另一件事:instance=i();if instance.dependens:
因为dependens
是一个类属性,所以您不必实例化该类。如果i.dependens:
,只需执行。如果始终使用元组,则可以完全删除,如果不是dep…
,因为如果没有依赖项,
的将永远无法执行。好的,知道你的代码只在依赖项有单字母名称的情况下工作,在其他情况下,你会遇到dependens='MultiLetterName'
vsdependens=('A','B')
的问题,你必须检查dep
是否是字符串。实际上,这是我首选的解决方案。我的同事非常反对在我们的项目中添加“另一个依赖项”。我认为最好避免重新发明轮子。尽管如此,当你重新发明轮子时,你学到了很多。你可以花时间学习很多关于其他人所做的事情,或者继续你的研究,这可能会帮助每个人学习新的东西。有很多算法。小心你的时间。我祝你一切顺利。谢谢你!
nx.draw(G,node_size=1000)