在不知道它们的值的情况下对 Python 数字列表进行排序,而只知道它们之间的关系
Posted
技术标签:
【中文标题】在不知道它们的值的情况下对 Python 数字列表进行排序,而只知道它们之间的关系【英文标题】:Sorting a Python list of numbers without knowing their values, but only their relationships among each other 【发布时间】:2020-03-23 13:01:16 【问题描述】:我有一个数字列表,我无法真正知道它们的真实值。假设list = [a,b,c,d]
。但我有关于它们如何相互关联的信息:
a > b
b > d
d > c
因此我可以推断出按降序排序的列表是[ a, b, d, c]
。
另一个更复杂的例子
relationships =
- g > b
- d > c > a
- d > g
- f > e > a
- f > e > d
在这种情况下,我们有多个可能的答案
Result:
[ [f, e, d, c, a, g, b],
[f, e, d, g, c, a, b],
[f, e, d, g, c, b, a],
... ]
仅举几例
我希望函数准确地返回我:所有可能答案的列表。关系是列表的列表,其中每个列表表示降序排序的关系。例如 relationships = [[a,b],[b,c]]
告诉我 "a > b" 和 "b > c" 。关系内部的内部列表不必具有相同的大小。如果输入无效/不可能,算法应该抛出错误。例如:
relationships = [[a,b],[b,c],[c,a]
是不可能的情况
什么是最有效的方法?我正在考虑使用图论,其中图不能是非循环的。例如 A -> B 意味着节点 A 去 B 意味着 A > B ,因此 B -> A 不能存在。有点像二叉树,但在这种情况下,我们只能允许 1 个,而不是每个节点允许 2 个子节点。
这是一个好主意吗?还是有人知道如何解决这个问题?
【问题讨论】:
这称为“拓扑排序”。我看到很多 Python 实现,尽管我不知道是否有任何支持您的“所有可能的答案”要求 - 典型的拓扑排序应用程序只需要一个答案。 这是Partial Order Sorting 上的相关答案,它证实您走在正确的轨道上,并提供更多信息和链接,例如Python Topological Sort 软件可能对您的任务有用。 哦,谢谢大家!知道它的术语会让整个世界变得不同。就是这样。我稍后会用我自己的实现来实现这个:) 【参考方案1】:你需要 3 个想法来理解这一点。
首先是通过卡恩算法进行拓扑排序。有关说明,请参见 https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm。我一直在通过该算法生成拓扑排序,并 yield
ing 每一个。
第二个是基于堆栈的编程。 Think Forth,或 RPN。这个想法是我有一堆动作。在每个步骤中,我都会从todo
列表中取出最重要的操作并执行此操作。如果我将项目添加到todo
列表中,那就像进行递归调用一样。在我的情况下,操作是choose
(尝试所有有意义的选择),add
(将一个元素添加到排序列表并记账),remove
(从排序列表中删除一个元素并记账)和try
(安排操作添加/选择/删除 - 但放置顺序相反,因为堆栈)。
第三个是生成器。我只是yield
多次,然后从我所在的位置继续。这可以循环,变成一个列表等。
这里是代码。
def topological_sorts (descending_chains):
arrive_to =
outstanding_arrivals =
for chain in descending_chains:
for x in chain:
if x not in arrive_to:
arrive_to[x] = set([])
outstanding_arrivals[x] = 0
for i in range(1, len(chain)):
arrive_to[chain[i-1]].add(chain[i])
for item in arrive_to:
for x in arrive_to[item]:
outstanding_arrivals[x] += 1
todo = [['choose', None]]
sorted_items = []
chosen = set([])
items = [x for x in arrive_to]
while len(todo):
action, item = todo.pop()
if action == 'choose':
choices = []
for x in outstanding_arrivals:
if x not in chosen and 0 == outstanding_arrivals[x]:
choices.append(x)
if 0 == len(choices):
if len(sorted_items) == len(items):
yield sorted_items
else:
print((choices, outstanding_arrivals, sorted_items))
break
else:
for item in choices:
todo.append(['try', item])
elif action == 'try':
chosen.add(item)
sorted_items.append(item)
todo.append(['remove', item])
todo.append(['choose', None])
todo.append(['add', item])
elif action == 'add':
chosen.add(item)
for x in arrive_to[item]:
outstanding_arrivals[x] -= 1
elif action == 'remove':
chosen.remove(item)
sorted_items.pop()
for x in arrive_to[item]:
outstanding_arrivals[x] += 1
else:
yield ('todo:', action, item)
这是第二个示例的使用方法。
for sort in topological_sorts([
['g', 'b'],
['d', 'c', 'a'],
['d', 'g'],
['f', 'e', 'a'],
['f', 'e', 'd']]):
print(sort)
【讨论】:
我希望我能给这个答案100个赞。这很有效,而且非常优雅。非常感谢。我不知道发电机的概念。真的很酷的东西。那么我基本上可以让一个函数表现得像一个迭代器吗? @RafaelSantos 没错。您可以在 Python 中手动构造迭代器,但只需使用带有yield
而没有 return
的函数即可为您完成所有的记账工作。【参考方案2】:
我在 Rosetta Code here 网站上为拓扑排序编写了 Python 代码。
输入是一个将节点映射到它所依赖的节点集的字典(在您的情况下,它们是“大于”的节点)。使用以下内容来表示您的示例依赖项:
a,b,c,d,e,f,g = 'abcdefg' # For ease of use
data = c:a, d:'cag', e:'ad', f:'ead', g:b # <node>:<is_greater_than>
dat = key: set(val) for key, val in data.items()
print ('\n'.join( toposort2(dat) ))
正确的输出如下,同一行上的节点可以以任何顺序出现在其上方其他行的节点之前:
a b
c g
d
e
f
注意您为示例提供的解决方案是错误的,因为您不能有 f,e,d 然后紧跟 b;它必须由 c 或 g(以任何顺序);然后是 a 或 b(以任何顺序)。
【讨论】:
以上是关于在不知道它们的值的情况下对 Python 数字列表进行排序,而只知道它们之间的关系的主要内容,如果未能解决你的问题,请参考以下文章
在不知道输入数量的情况下计算最小、最大和平均输入值的最佳方法? [关闭]