具有元组和Dicts的优先级队列

时间:2016-10-23 16:39:14

标签: python algorithm python-3.x dictionary

问题陈述

目前我正在Python中实现A *搜索算法(针对特定问题进行了大量修改)。作为问题的一个组成部分,我需要快速访问LIFO顺序中最小的启发式值。 Python的优先级队列看起来最好。我的数据结构已经是一个带有父关系的字典列表,所以能够对它们进行排序会很好。但是,我似乎遇到了困难。例如,我有一个新的字典示例:

queue = PriorityQueue() # Could be instantiated based on an existing list of dicts
exampleDict = {"x": 10, "y": 20, "f": 123, "parent":{}}

我希望能够通过元组或类似形式将此字典插入优先级队列

queue.put((exampleDict["f"], exampleDict))

请注意,我无法尝试外部库,所以我正在寻找更原生的py3解决方案。

发生了什么

我尝试过对我来说很明显的解决方案。阅读文档,我发现Python允许一个元组,其中元组中的第二项是字典,第一项是优先级:

parent = {'x': 0, 'y': 0, 'parent': False, 'f': 0, 'g': 50, 'wallPassed': 0}
something = (1, {'h': 9, 'x': 0, 'y': 1, 'parent': parent, 'f': 60, 'g': 51, 'wallPassed': 0})
somethingElse = (1, {'h': 9, 'x': 1, 'y': 0, 'parent': parent, 'f': 60, 'g': 51, 'wallPassed': 1})
test = PriorityQueue()
test.put(something)
test.put(somethingElse)

插入一个值时有效,但是当我插入另一个值失败时

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    test.put(somethingElse)
  File "C:\Users\champloo11\AppData\Local\Programs\Python\Python35-32\lib\queue.py", line 143, in put
    self._put(item)
  File "C:\Users\champloo11\AppData\Local\Programs\Python\Python35-32\lib\queue.py", line 227, in _put
    heappush(self.queue, item)
TypeError: unorderable types: dict() < dict()

有什么可以做的吗?关于这个问题的文档似乎没有那么多,或者在没有冻结的情况下解决它。

3 个答案:

答案 0 :(得分:10)

问题是字典是Python中无法解决的类型。也就是说,如果你尝试运行类似的东西:

>>> {'alpha': 1} < {'beta': 2}
----> 1 {'alpha': 1} < {'beta': 2}

TypeError: unorderable types: dict() < dict()

你会得到TypeError。您尝试使用的技巧是将字典包装成一个元组,其第一个元素可订购的 - 类似于数字。然后我们可以比较它们:

>>> (1, {'alpha': 1}) < (2, {'beta': 2})
True

现在看看Python如何比较元组是值得的。首先,Python比较每个元组的第一个条目。在这种情况下,1 < 2和Python返回True。但是如果第一个条目没有被排序 - 比如说它们是相同的 - 那么Python会去比较第二个条目。例如

>>> (1, 42) < (2, 7)
True
>>> (1, 42) < (1, 88)  # 42 < 88
True
>>> (1, 42) < (1, 7)   # 42 >= 7
False

所以上面的例子中发生的是你有两个具有相同第一个条目的元组,每个元组的第二个条目是一个字典。因此,Python比较前两个条目,无法确定它们的顺序,然后尝试比较第二个条目,这些条目是字典且无法排序。在一个例子中,

>>> (1, {'alpha': 1}) < (2, {'beta': 2})
True
正如我们上面所见,

工作得很好。但是更改右边元组的第一个条目会产生错误:

>>> (1, {'alpha': 1}) < (1, {'beta': 2})
----> 1 (1, {'alpha': 1}) < (1, {'beta': 2})
TypeError: unorderable types: dict() < dict()

那么什么是正确的解决方案?如果您有办法为每个字典分配唯一优先级,那么元组方法将起作用。但每个元组的第一个条目必须是唯一的!否则,Python会使用第二个条目进行比较,这些条目是字典,您将再次遇到相同的问题。

如果那是不可能或不可取的,那么我们必须给Python一种比较这两个词典的方法。一种方法是创建PriorityEntry类并定义其__lt__方法。要创建此类的实例,您可以提供可以订购的优先级和无需可订购的数据。然后__lt__仅按其优先级排序该类的两个实例,而不是按其数据排序。那就是:

class PriorityEntry(object):

    def __init__(self, priority, data):
        self.data = data
        self.priority = priority

    def __lt__(self, other):
        return self.priority < other.priority

等等

>>> PriorityEntry(1, {'alpha': 1}) < PriorityEntry(1, {'beta': 2})
False
>>> PriorityEntry(1, {'alpha': 1}) < PriorityEntry(2, {'beta': 2})
True

或者,使用您的数据:

parent = {'x': 0, 'y': 0, 'parent': False, 'f': 0, 'g': 50, 'wallPassed': 0}
something = PriorityEntry(1, {'h': 9, 'x': 0, 'y': 1, 'parent': parent, 'f': 60, 'g': 51, 'wallPassed': 0})
somethingElse = PriorityEntry(1, {'h': 9, 'x': 1, 'y': 0, 'parent': parent, 'f': 60, 'g': 51, 'wallPassed': 1})
test = PriorityQueue()
test.put(something)
test.put(somethingElse)

现在可以在不引发错误的情况下运行。

答案 1 :(得分:6)

优先级队列中不可共享数据的常见解决方案是向元组添加一个额外值,该值在两个不同的条目中永远不会相等。它将打破具有相同优先级的两个项目之间的“联系”,而不需要可订购数据。稳定增长的整数是一种简单的方法:

import heapq
import itertools

unorderable_data = {"foo": "bar"}

heap = []
counter = itertools.count()

entry1 = (2, next(counter), unorderable_data)
entry2 = (1, next(counter), unorderable_data)
entry3 = (2, next(counter), unorderable_data)

heapq.heappush(heap, entry1)
heapq.heappush(heap, entry2)
heapq.heappush(heap, entry3)

priority, tie_breaker, data = heapq.heappop(heap)

print(priority, data) # prints 1, {"foo": "bar"}

除了展示如何向元组添加打破平局项之外,我还在此演示使用heapq模块在​​列表中实现优先级队列,而不是创建一个实例。 queue.PriorityQueue课程。整个queue模块旨在使多线程程序中的序列化通信成为可能,因此如果您正在创建单线程应用程序,则其所有类都会有一些与锁相关的开销。 PriorityQueue类使用了heapq模块,我建议直接使用它。

答案 2 :(得分:1)

就目前而言,你正试图比较元组,这间接地试图比较dict s(这是无与伦比的)。绕过这很容易。假设你写下面的课程

class use_only_first(object):
    def __init__(self, first, second):
        self._first, self._second = first, second

    def __lt__(self, other):
        return self._first < other._first

并在heapqqueue中使用此类的实例。它将忽略第二个参数。请注意,这仅使用标准库中的模块。

P.S。如果您对并发方面感兴趣,那么您应该使用queue.PriorityQueue。否则heapq会更有效率。