我有一个这样的DataFrame:
vals = {"operator": [1, 1, 1, 2, 3, 5], "nextval": [2, 3, 6, 4, 5, 6]}
df = pd.DataFrame(vals)
operator nextval
0 1 2
1 1 3
2 1 6
3 2 4
4 3 5
5 5 6
我想做的是使用运算符和nextval,而不是最短路径,从起点(如1)和终点(如6)获取所有可能路径的列表。 输出可能很灵活,但我正在寻找类似或传达这种信息的东西:
1 -> 6
1 -> 2 -> 4
1 -> 3 -> 5 -> 6
我能够将其关闭,但由于字典无法处理2个相同的键,因此不确定如何正确执行递归:
import pandas as pd
vals = {"operator": [1, 1, 1, 2, 3, 5], "nextval": [2, 3, 6, 4, 5, 6]}
df = pd.DataFrame(vals)
df1 = df.set_index("operator")
dictvals = {}
for x in df1.index.unique():
dictvals[x] = []
df2 = df1.loc[x]
if isinstance(df2, pd.DataFrame):
for idx, rowdata in df2.iterrows():
dictvals[x].append(rowdata["nextval"])
else:
dictvals[x] = df2[0]
print(dictvals)
{1: [2, 3, 6], 2: 4, 3: 5, 5: 6}
答案 0 :(得分:4)
使用networkx
进行检查,您需要一个方向图,其中有'root'
至'leaf'
的路径
import networkx as nx
G=nx.from_pandas_edgelist(df,source='operator',target='nextval', edge_attr=None, create_using=nx.DiGraph())
road=[]
for n in G:
if G.out_degree(n)==0: #leaf
road.append(nx.shortest_path(G, 1, n))
road
Out[82]: [[1, 2, 4], [1, 3, 5, 6]]
更新
import networkx as nx
G=nx.from_pandas_edgelist(df,source='operator',target='nextval', edge_attr=None, create_using=nx.DiGraph())
road=[]
for n in G:
if G.out_degree(n)==0: #leaf
road.append(list(nx.all_simple_paths(G, 1, n)))
road
Out[509]: [[[1, 3, 5, 6], [1, 6]], [[1, 2, 4]]]
答案 1 :(得分:4)
让我们尝试动手解决方案,因为考虑这种递归算法是有教育意义的。 (当然,仅在现实世界中使用现有的库是适当的;它可能更具容错性。)
您显示的代码可建立图形本身的可识别表示形式,但是为了保持一致性,即使在只有一个后继节点的情况下,也最好使用列表(或集合或元组)作为值。我认为这里的集合最有意义,因为如果输入中有重复的条目,那么我们应该丢弃重复的图节点。因此,让我们假设我们开始于:
graph = {1: {2, 3}, 2: {4}, 3: {5}, 5: {6}}
我们已经同意将自己局限于考虑有向无环图。我建议可以如下递归找到来自根节点的路径:递归地检查 each 后继节点的 each 路径;累积这些结果,并在每个结果前加上从根到相应后继项的链接。
当然,当我们编写递归代码时,我们希望避免副作用,因为它们使推理变得更加困难。因此,让我们改为说:对于每个后继者,对于该后继者的每个拍子,定义为(从节点到后继者的链接)+(从后继者到末端的路径)的所有路径的累积。当然,我们表示“从节点到后继者的链接”的方式只是当前节点的名称和箭头。我们从递归中获取其余路径,包括后继名称。
然后,我们需要一个基本情况:如果没有后继,那么我们只有一条从此处到末尾的路径(因为我们位于末尾),它本身就是那个节点名。如果图形中的死角用空集表示,那么对于我们的代码而言,这将更加简单。但是生成图形显然很容易,因为省略了这些键。因此,我们在进行检查时将依靠dict.get
而不是索引。
好吧,第一部分听起来对我来说很像一个列表理解(带有两个for
子句。)对于基本情况,为了匹配它,我们需要一个包含一个路径的列表。这给了我们:
def paths(graph, root):
successors = graph.get(root, set())
if not successors:
return [str(root)] # a single path starting here and going nowhere.
return [
f'{root} -> {path}'
for successor in successors
for path in paths(graph, successor)
]
让我们尝试一下:
>>> paths({1: {2, 3}, 2: {4}, 3: {5}, 5: {6}}, 1)
['1 -> 2 -> 4', '1 -> 3 -> 5 -> 6']
或者,您可以使用生成器表达式而不是列表推导,甚至可以将其写为递归生成器(使用yield
和yield from
)。
(如果我们足够脸颊,可以使用条件表达式来继续进行功能编程主题:)
def paths(graph, root):
successors = graph.get(root, set())
return [
f'{root} -> {path}'
for successor in successors
for path in paths(graph, successor)
] if successors else [str(root)]