【问题标题】:Python: Priority queue does'nt keep order on same priority elementsPython:优先级队列不保持相同优先级元素的顺序
【发布时间】:2017-12-25 14:45:16
【问题描述】:

我正在使用python的Queue.PriorityQueue,遇到了以下问题:当向队列中插入几个具有相同优先级的元素时,我希望队列按顺序为它们提供服务插入(FIFO)。出于某种原因,情况并非如此:

>>> from Queue import PriorityQueue
>>>
>>> j1 = (1, 'job1')
>>> j2 = (1, 'job2')
>>> j3 = (1, 'job3')
>>> j4 = (1, 'job4')
>>> 
>>> q = PriorityQueue()
>>> q.put(j1)
>>> q.put(j2)
>>> q.put(j3)
>>> q.put(j4)
>>> q.queue
[(1, 'job1'), (1, 'job2'), (1, 'job3'), (1, 'job4')]
>>> q.get()
(1, 'job1')
>>> q.queue
[(1, 'job2'), (1, 'job4'), (1, 'job3')]

从例子中可以看出,在一个get()之后,顺序已经混杂了。 什么原因?如何克服(保持相同prio元素的顺序)?

编辑

我被要求添加一个示例,表明 q.get() 实际上将 FIFO 排序搞砸了,所以这里有一个详细的示例:

class Job(object):
    def __init__(self, type_, **data):
        self.type_ = type_
        self.priority = 0 if self.type_ == 'QUIT' else 1
        self.data = data

    def __cmp__(self, other):
        return cmp(self.priority, other.priority)

    def __repr__(self):
        return 'Job("' + self.type_ + '", data=' + repr(self.data) + ')' 

q = PriorityQueue()
q.put(Job('Build'))
q.put(Job('Clean'))
q.put(Job('QUIT'))
q.put(Job('Create'))
q.put(Job('Build'))
q.put(Job('Clean'))

现在我将逐个出列元素。预期结果:QUIT先出,后出,FIFO排序:Build、Clean、Create、Build、Clean:

>>> q.get()
Job("QUIT", data={})
>>> q.get()
Job("Build", data={})
>>> q.get()
Job("Clean", data={})
>>> q.get()
Job("Build", data={}) # <<---
>>> q.get()
Job("Clean", data={})
>>> q.get()
Job("Create", data={})

【问题讨论】:

  • 您为什么会期望广告订单?
  • 虽然不是完全相同的数据结构,但 OrderedDict 确实记得插入顺序。
  • @BradSolomon 为什么使用python-2.7 标签?它也发生在 Python 3 中。
  • @StefanPochmann 因为 PriorityQueue 是 Queue 的子类,所以我假设 FIFO,除非优先级获胜
  • @OmerDagan 好的,我想这是合理的。 (顺便说一句,请注意我在您接受后添加到我的答案中的第二段。)

标签: python priority-queue


【解决方案1】:

优先队列"are often implemented with heaps",Python 也不例外。正如文档所说,它是"using the heapq module"。堆自然不会提供稳定性。这也是为什么要对"is not a stable sort" 进行堆排序。如果你想要稳定,你需要自己去执行。幸运的是,它就像存储条目 "as 3-element list including the priority, an entry count, and the task" 一样简单。

请注意,您给 Python 的优先级队列 优先级和任务,但队列不关心。它不认为这两个值是优先级和任务。它只是将这对视为一个“项目”,它甚至从不查看它。只有我们用户认为这对是优先级和任务。所以你也可以单独给它任务字符串,而不需要额外的优先级。队列甚至不会注意到。它不会尝试提取某些优先级。对于它的优先级,它只是询问整个项目是否比另一个小。这就是为什么,当您想不仅按照任务的自然顺序(例如,字符串 'job1' 小于字符串 'job2')对任务进行优先级排序时,您需要使用优先级和任务的元组。元组按字典顺序排列,因此如果a 小于c 或者它们相等且b 小于d,则(a, b) 小于(c, d)。因此,当队列询问这样一个元组是否小于另一个元组时,元组会查看自身并首先考虑优先级,然后可能是第二个任务。

另外,使用q.queue,您正在检查队列的底层数据结构。你不应该关心那个。不知道为什么它甚至可以访问。但是,如果您确实检查它,则需要将其视为堆,而不是将其视为排序列表。并不是您说的“订单混杂”,而是您误解了该列表。无论如何...您应该关心的顺序是您实际获得的顺序。与q.get()。如果您只是使用q.get() 获得该示例的所有四个项目,您会发现它确实在您的广告订单中将它们提供给您。虽然那是因为您按排序顺序插入它们并且它们只有一种可能的顺序,因为没有相等的项目。你会得到(1, 'job1') first not,因为它是最先插入的,而是因为它是四个元组中最小的(因为优先级相同,'job1' 是四个字符串中最小的一个)。你会得到(1, 'job2') second not,因为它是第二个插入的,而是因为它是第二小的项目。等等。如果您以任何其他顺序插入它们,您仍然会按(1, 'job1')(1, 'job2')(1, 'job3')(1, 'job4') 的顺序获取它们。

关于您添加的示例:您的 Job 对象仅按优先级进行比较。而那些 Build、Clean、Create、Build 和 Clean 对象都具有相同的优先级。所以就队列而言,他们都是平等的!这不像你的第一个例子,你的四个元组只允许一个可能的顺序。所以我们回到我一开始所说的,堆不会自然地提供稳定性,如果你想要稳定性,你应该添加一个条目计数。查看我在那里链接的解释和食谱。它使用列表作为堆并使用heapq 函数,但您可以轻松地将其调整为使用PriorityQueue。虽然不是那些单独的顶级帮助函数,但最好将自己的 StablePriorityQueue class 定义为 PriorityQueue 的子类或包装器。

【讨论】:

  • 关于您的第二段-无论我使用q.queue 还是q.get(),它都不会保留相同优先级内的排序
  • @OmerDagan 你错了。不过,不确定 如何 你错了。我需要看看你现在在做什么。你能展示一下吗?这是它按照我说的做的演示:ideone.com/jXj737 检查代码并在那里输出。
  • 添加了一个显示问题的详细示例(使用 q.get() 而不是 q.queue)
  • @OmerDagan 你不只是使用q.get()。你也完全改变了这个例子。我说的是你原来的例子。在您的新示例中,您的订单很容易受到攻击。因为与您的原始示例不同,有“相等”的项目。所以你回到我的第一段,堆不能自然地提供稳定性,你应该添加一个条目计数。再看一遍,除了第一段,我几乎重写了所有内容。
【解决方案2】:

正如here 所解释的,Python PriorityQueue 是使用binary heap 实现的。

二叉堆是一棵二叉树,其中每个节点的值都等于或大于其两个子节点的值。因此,在二叉堆中,根始终包含最小值。删除最小节点后,堆将重新组织,以便基本堆属性仍然有效。

堆通常使用数组实现,其中a[k]a[2*k]a[2*k+1] 的父级。在 Python 中,q.queue 就是这个数组。从堆中删除元素后,数组会以不保留原始顺序的方式重新排序。

【讨论】:

    【解决方案3】:

    其他 2 个答案解释了会发生什么。

    虽然我想为您提供另一种表示,以帮助您更好地理解。

    我从这个documentation page 拍摄了一张关于heapq 的快照。首先可以看到PriorityQueue使用了一个堆here

    现在,上图。

    heappop

    在这张图片中,当你弹出第一个项目 0 (job1) 时,1 ('job2') 将取代它,然后,3 (job4) 将取代 @987654331 @ (job2) 地点。我们应该总结说这是一种正常的行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-21
      • 1970-01-01
      • 2011-10-18
      • 2021-11-29
      • 2013-02-13
      • 1970-01-01
      • 2016-09-23
      • 2023-04-02
      相关资源
      最近更新 更多