这更像是一个概念性的编程问题,所以请耐心等待:
假设您有电影中的场景列表,并且每个场景可能会或可能不会参考同一电影中的过去/未来场景。我正在尝试找到最有效的排序这些场景的算法。当然,可能没有足够的信息让场景完全排序。
这里有一些Python中的示例代码(几乎是伪代码)来澄清:
class Reference:
def __init__(self, scene_id, relation):
self.scene_id = scene_id
self.relation = relation
class Scene:
def __init__(self, scene_id, references):
self.id = scene_id
self.references = references
def __repr__(self):
return self.id
def relative_sort(scenes):
return scenes # Algorithm in question
def main():
s1 = Scene('s1', [
Reference('s3', 'after')
])
s2 = Scene('s2', [
Reference('s1', 'before'),
Reference('s4', 'after')
])
s3 = Scene('s3', [
Reference('s4', 'after')
])
s4 = Scene('s4', [
Reference('s2', 'before')
])
print relative_sort([s1, s2, s3, s4])
if __name__ == '__main__':
main()
目标是在这种情况下relative_sort
返回[s4, s3, s2, s1]
。
如果它有用,我可以分享我对该算法的初步尝试;我对它的蛮力有点尴尬。另外,如果你想知道,我正在尝试解读电影“穆赫兰道”的情节。
仅供参考:Python标签仅在此处,因为我的伪代码是用Python编写的。
答案 0 :(得分:1)
您要查找的算法是topological sort:
在计算机科学领域,有向图的拓扑排序或拓扑排序是其顶点的线性排序,使得对于从顶点u到顶点v的每个有向边uv,u在排序中位于v之前。例如,图的顶点可以表示要执行的任务,并且边可以表示一个任务必须在另一个之前执行的约束;在这个应用程序中,拓扑排序只是任务的有效序列。
您可以使用图表库轻松计算,例如,networkx
,它实现topological_sort
。首先,我们导入库并列出场景之间的所有关系 - 即图中的所有有向边
>>> import networkx as nx
>>> relations = [
(3, 1), # 1 after 3
(2, 1), # 2 before 1
(4, 2), # 2 after 4
(4, 3), # 3 after 4
(4, 2) # 4 before 2
]
然后我们创建一个有向图:
>>> g = nx.DiGraph(relations)
然后我们进行拓扑排序:
>>> nx.topological_sort(g)
[4, 3, 2, 1]
答案 1 :(得分:0)
我已将修改后的代码包含在我的答案中,这解决了当前(小)问题,但没有更大的样本问题,我不确定它的扩展程度。如果您提供了您尝试解决的实际问题,我很乐意测试并优化此代码,直到它解决该问题,但如果没有测试数据,我将不再进一步优化此解决方案。
对于初学者,我们将引用跟踪为集合,而不是列表。
我们计算最小和最大位置:
pass
中的tighten_bounds(self)
以尝试收紧单个场景的边界(如果有效,则将anything_updated设置为true)。魔法在get_possible_orders
代码:
class Reference:
def __init__(self, scene_id, relation):
self.scene_id = scene_id
self.relation = relation
def __repr__(self):
return '"%s %s"' % (self.relation, self.scene_id)
def __hash__(self):
return hash(self.scene_id)
def __eq__(self, other):
return self.scene_id == other.scene_id and self.relation == other.relation
class Scene:
def __init__(self, title, references):
self.title = title
self.references = references
self.min_pos = 0
self.max_pos = None
def __repr__(self):
return '%s (%s,%s)' % (self.title, self.min_pos, self.max_pos)
inverse_relation = {'before': 'after', 'after': 'before'}
def inverted_reference(scene, reference):
return Reference(scene.title, inverse_relation[reference.relation])
def is_valid_addition(scenes_so_far, new_scene, scenes_to_go):
previous_ids = {s.title for s in scenes_so_far}
future_ids = {s.title for s in scenes_to_go}
for ref in new_scene.references:
if ref.relation == 'before' and ref.scene_id in previous_ids:
return False
elif ref.relation == 'after' and ref.scene_id in future_ids:
return False
return True
class Movie:
def __init__(self, scene_list):
self.num_scenes = len(scene_list)
self.scene_dict = {scene.title: scene for scene in scene_list}
self.set_max_positions()
self.add_inverse_relations()
self.bound_min_max_pos()
self.can_tighten = True
while self.can_tighten:
self.tighten_bounds()
def set_max_positions(self):
for scene in self.scene_dict.values():
scene.max_pos = self.num_scenes - 1
def add_inverse_relations(self):
for scene in self.scene_dict.values():
for ref in scene.references:
self.scene_dict[ref.scene_id].references.add(inverted_reference(scene, ref))
def bound_min_max_pos(self):
for scene in self.scene_dict.values():
for ref in scene.references:
if ref.relation == 'before':
scene.max_pos -= 1
elif ref.relation == 'after':
scene.min_pos += 1
def tighten_bounds(self):
anything_updated = False
for scene in self.scene_dict.values():
pass
# If bounds for any scene are tightened, set anything_updated back to true
self.can_tighten = anything_updated
def get_possible_orders(self, scenes_so_far):
if len(scenes_so_far) == self.num_scenes:
yield scenes_so_far
raise StopIteration
n = len(scenes_so_far)
scenes_left = set(self.scene_dict.values()) - set(scenes_so_far)
valid_next_scenes = set(s
for s in scenes_left
if s.min_pos <= n <= s.max_pos)
# valid_next_scenes = sorted(valid_next_scenes, key=lambda s: s.min_pos * self.num_scenes + s.max_pos)
for s in valid_next_scenes:
if is_valid_addition(scenes_so_far, s, scenes_left - {s}):
for valid_complete_sequence in self.get_possible_orders(scenes_so_far + (s,)):
yield valid_complete_sequence
def get_possible_order(self):
return self.get_possible_orders(tuple()).__next__()
def relative_sort(lst):
try:
return [s.title for s in Movie(lst).get_possible_order()]
except StopIteration:
return None
def main():
s1 = Scene('s1', {Reference('s3', 'after')})
s2 = Scene('s2', {
Reference('s1', 'before'),
Reference('s4', 'after')
})
s3 = Scene('s3', {
Reference('s4', 'after')
})
s4 = Scene('s4', {
Reference('s2', 'before')
})
print(relative_sort([s1, s2, s3, s4]))
if __name__ == '__main__':
main()
答案 2 :(得分:0)
正如其他人所指出的,你需要拓扑排序。定向关系形成边缘的有向图的深度优先遍历就是您所需要的。访问后期订单。这与拓扑排序相反。所以要获得topo排序,只需反转结果。
我已将您的数据编码为一对列表,显示了之前已知的内容。这只是为了保持我的代码简短。您可以轻松遍历类列表以创建图形。
请注意,要使topo排序有意义,要排序的集必须满足partial order的定义。你的很好。时间事件的顺序约束自然满足定义。
请注意,使用循环创建图表是完全可能的。没有像这样的图表。此实现不会检测周期,但可以很容易地对其进行修改。
当然你可以使用一个库来获得topo排序,但那里的乐趣在哪里?
from collections import defaultdict
# Before -> After pairs dictating order. Repeats are okay. Cycles aren't.
# This is OP's data in a friendlier form.
OrderRelation = [('s3','s1'), ('s2','s1'), ('s4','s2'), ('s4','s3'), ('s4','s2')]
class OrderGraph:
# nodes is an optional list of items for use when some aren't related at all
def __init__(self, relation, nodes=[]):
self.succ = defaultdict(set) # Successor map
heads = set()
for tail, head in relation:
self.succ[tail].add(head)
heads.add(head)
# Sources are nodes that have no in-edges (tails - heads)
self.sources = set(self.succ.keys()) - heads | set(nodes)
# Recursive helper to traverse the graph and visit in post order
def __traverse(self, start):
if start in self.visited: return
self.visited.add(start)
for succ in self.succ[start]: self.__traverse(succ)
self.sorted.append(start) # Append in post-order
# Return a reverse post-order visit, which is a topo sort. Not thread safe.
def topoSort(self):
self.visited = set()
self.sorted = []
for source in self.sources: self.__traverse(source)
self.sorted.reverse()
return self.sorted
则...
>>> print OrderGraph(OrderRelation).topoSort()
['s4', 's2', 's3', 's1']
>>> print OrderGraph(OrderRelation, ['s1', 'unordered']).topoSort()
['s4', 's2', 's3', 'unordered', 's1']
第二个调用显示您可以选择传递要在单独列表中排序的值。您可能但在关系对中已经没有提及值。当然,订单对中未提及的那些可以自由地出现在输出中的任何位置。