在不知道它们的值的情况下对 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。我一直在通过该算法生成拓扑排序,并 yielding 每一个。

第二个是基于堆栈的编程。 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 数字列表进行排序,而只知道它们之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用乘法的情况下对数字进行平方?

在不知道输入数量的情况下计算最小、最大和平均输入值的最佳方法? [关闭]

在不破坏顺序的情况下对列进行分组

如何在不指定集群数量的情况下对列表中的项目进行集群

如何在不删除先前相同值的情况下选择具有重复项的列表中的特定数字?

如何在不删除重复值的情况下对数据集进行分组