问题描述
我有一个用整数填充的列表。这些整数表示图中的节点,主列表中的列表表示图中的循环。我想按照列表列表的顺序提取一组唯一的节点 - 每个循环中的一个节点。
示例:
我知道,不可能有一个只有两个节点的循环,但这是我想出的最简单的非平凡示例,它应该清楚我在寻找什么。
cycles = [[11,22,55],[22,44],[11,33],33]]
result = [11,44,33,22]
另一个可能的结果是 [22,11,33]
我的解决方案:
我到目前为止尝试的是使用 itertools.product
来遍历列表列表中的所有元素组合,直到找到与主列表中列表数量相同长度的集合。
def find_random_unique_combination_from_cycles(cycles):
only_one = dict()
for ci,cy in enumerate(cycles):
for element in cy:
if len([element for cyc in cycles if element in cyc]) == 1:
only_one.update({element: ci})
prod_cycles = [cycle for ci,cycle in enumerate(cycles) if ci not in only_one.values()]
cycle_length = len(prod_cycles)
result = []
for combi in it.product(*prod_cycles,repeat=1):
if len(set(combi)) == cycle_length:
result = list(combi)
break
for element,index in only_one.items():
result.insert(index,element)
return result
问题:
此解决方案非常适用于上述示例和类似情况。但是对于具有更多和更大周期的较大图形,它在适当的运行时找不到解决方案(我不得不停止执行包含约 200 个周期的列表)。我还尝试通过删除所有只有一个唯一元素的循环来减小列表列表的大小,但这并没有多大帮助。 有没有更好更快的方法从每个列表列表中找到一个唯一的元素?
非常感谢帮助!提前致谢!
解决方法
cycles = [[11,22],[22,44],[11,33],33]]
from itertools import product
a=list(product(*cycles))
[list(i) for i in a if len(i) == len(set(i))]
输出:
[[11,44,33,11,33]]
如果您想提高效率,可以查看 networkx
模块。
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
cycles = [(11,22),(22,44),(11,33),33)]
G.add_edges_from(cycles)
nx.draw(G,with_labels=True,font_weight='bold')
G.nodes
G.nodes
返回 NodeView((11,22,33))
我建议使用递归生成器方法将使用过的数字集带到下一级递归:
def getPath(cycles,used=None):
if not cycles: yield [];return # end of path
if used is None: used = set() # track used numbers
for n in cycles[0]: # take from first sub list
if n in used: continue # skip used,recurse,tagging used n
yield from ([n]+p for p in getPath(cycles[1:],used|{n}))
输出:
cycles = [[11,55],33]]
for path in getPath(cycles): print(path)
[11,22]
[22,33]
[55,22]
[55,22]
如果你想让路径随机出来,你可以在函数的开头添加这一行(并导入 random 模块):
if used is None: cycles = [random.sample(c,len(c)) for c in cycles]
这将允许您使用 next() 函数获取随机路径,而无需遍历所有组合:
randomPath = next(getPath(cycles))
请注意,生成器使用了朴素的蛮力遍历。有优化空间。
例如,可以检查循环中不同的未使用数字是否足以覆盖子列表的数量。这将允许更早地短路遍历分支。
...
if len(set().union(*cycles)-used)<len(cycles): return
for n in cycles[0]:
...
或者,可以从每个递归级别的循环列表中删除使用过的数字。
def getPath(cycles):
if not cycles: yield [];return # end of path
if not all(cycles): return # no path when there is an empty sublist
for n in cycles[0]: # take from first sub list
nextCycles = [ [r for r in c if r!=n] for c in cycles[1:] ]
yield from ( [n] + p for p in getPath(nextCycles) )