这里主要记录下做题过程中遇到的新知识点:
dataclasses模块中的dataclass、field,以及queue中的PriorityQueue
PriorityQueue的参考见链接:
https://docs.python.org/3/library/queue.html
先讲一下python中优先队列的实现原理:优先队列实际上是使用堆结构实现的,能够将搜索的时间复杂度从 O ( k ) O(k) O(k)降到 O ( l o g k ) O(logk) O(logk)。看一下官方文档中对优先队列的描述:
The lowest valued entries are retrieved first。
可以发现,这里的优先队列实际上就是小根堆。大根堆也能用优先队列实现(加个负号就搞定了)。当然我们知道python中有一个高级数据结构是heapq,是专门用来处理堆结构的。个人认为有些情况下可以用优先队列来实现heapq的功能,这样记忆负担小一些。
在介绍PriorityQueue时候,有这样一句话:
A typical pattern for entries is a tuple in the form:(priority_number, data). If the data elements are not comparable, the data can be wrapped in a class that ignores the data item and only compares the priority number:
然后给了一段示例代码:
from dataclasses import dataclass, field
from typing import Any
@dataclass(order=True)
class PrioritizedItem:
priority: int
item: Any=field(compare=False)
好了,这段简单的示例代码中涉及到了如下的知识点:
现在我们依次解决这些知识点。
首先dataclass、field都是从dataclasses中导入的,关于dataclasses的介绍参考链接:
https://docs.python.org/zh-cn/3/library/dataclasses.html
简单来说,这个模块提供了一个装饰器和一些函数,用于自动添加生成的special methods,比如
__init__()和__repr__()
到用户定义的类。这些方法是自动添加的,不会像我们自己定义类的时候显示添加。
3.7之后的功能:
提供了模块级的装饰器、类和函数
首先看dataclass的定义:
dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
这个函数是decorator,用于将生成的special method添加到类中。
dataclass()装饰器检查类以找到field,所谓的field定义为具有类型标注的类变量。(后面具体例子在详细说明)
所有生成的方法中的字段顺序是它们在类定义中出现的顺序。(同样后面详细说明)
这里主要解释下几个关键参数的意义:
repr: 生成的repr字符串将具有类名以及每个字段的名称和repr,按照它们在类中定义的顺序。不包括标记为从repr中排除的字段。(和field中的repr参数配合使用会很有趣,具体后面例子说明)
eq:此方法将类作为其字段的元组按顺序比较。(刚开始看的时候很懵,举个例子就发现很好理解了,具体后面详细说明)
接下来看看field的定义:
field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)
有些数据类功能需要额外的每字段信息。为了满足这种对附加信息的需求,你可以通过调用提供的field()函数来替换默认字段值。(直接看这个会有点懵,同样后面举例详细说明)
记录下field的几个关键参数:
repr:该字段包含在生成的repr()方法返回的字符串中,也就是说这里设置成False,最终显示的字符串中没有这个字段的名称和值。
compare:为True,则该字段包含在生成的相等性和比较方法中。
讲到这,先举几个例子把上面的概念熟悉下。(不过还得先理解类装饰器的概念,参考链接:https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p12_using_decorators_to_patch_class_definitions.html,这里先不展开)
from dataclasses import dataclass, field
from queue import PriorityQueue
@dataclass(order=True)
class Item:
num1: int#field,这就是field;类型标注:num1:int,int就是对类变量num1的类型标注
num2: int
str_: str
item1 = Item(5, 3, 'c')#自动调用__init__(),即self.num1=5;self.num2=3;...
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put()
q.put()
首先看一看repr的含义:
>>>item1
Item(num1=5, num2=3, str_='c')#顺序是在类中添加的顺序,默认值都是要添加到repr字符串中的
修改一下item的定义:
@dataclass(order=True)
class Item:
num1: int
num2: int=field(repr=False)
str_: str
item1 = Item(5,3,'c')
看看item1会变成什么?
>>>item1
Item(num1=5, str_='c')
好,到这理解了repr参数的含义了。
接下来理解compare参数的含义;不过在理解这个参数含义之前,需要对优先队列理解下。
from dataclasses import dataclass, field
from queue import PriorityQueue
@dataclass(order=True)
class Item:
num1: int
num2: int
str_: str
item1 = Item(5, 3, 'c')
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put()
q.put()
优先队列有一个方法叫get(),即取出优先队列队首的entry,即最小entry。
前面介绍优先队列有这样一句话:
A typical pattern for entries is a tuple in the form:(priority_number, data).以及将类作为其字段的元组按顺序比较。
这个例子中的entry变成了(num1, num2, str_),首先按num1进行比较,num1相同的情况按num2进行比较,同理按str_进行比较。
看代码
>>>q,get()
Item(num1=3, num2=4, str_='b')
很好理解
接下来我们进行一些改动,
from dataclasses import dataclass, field
from queue import PriorityQueue
@dataclass(order=True)
class Item:
num1: int
num2: int=field(compare=False)#即这个类字段不参与比较
str_: str
item1 = Item(3, 5, 'a')
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put(item1)
q.put(item2)
我们先想一想,q.get()会是什么结果?简单来说item1和item2谁小?
由于item1和item2第一个元素相同,我们需要按顺序往后面比较,但是发现num2字段的compare为False,即不参与比较,于是我们比较第三个字段str_,发现item1小,但是如果num2字段的compare为True的话应该是item2小。
看看结果:
>>>q.get()
Item(num1=3, num2=5, str_='a')
OK,到这我们把dataclass、field的含义理解得差不多了。
简单记录下Any的含义:Any 可以是任何类型的数据;在python中,任意的对象都是Object的子类,但是反过来不成立;任何对象都是Any的subclass,同样,Any也是任何对象的subclass。
参考链接:https://docs.python.org/3/library/typing.html
接下来记录下leetcode上的一道题目,合并K个有序链表:
参考链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/
官方视频解答中python3写的优先队列是有问题的。这里给出自己的正确答案:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
from dataclasses import dataclass, field
from typing import Any
from queue import PriorityQueue
@dataclass(order=True)
class PriorityItem:
priority_number: int
item: Any=field(compare=False)
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
head = p = ListNode(-1)
q = PriorityQueue()
for node in lists:
if node:
q.put(PriorityItem(node.val, node))
while q.qsize()>=2:#不能用while q来判断,这样会显示死循环,而且即使q里面没有东西,也不是返回False
temp = q.get()
val, node = temp.priority_number, temp.item
p.next = node
p = p.next
node = node.next
if node:
q.put(PriorityItem(node.val, node))
if not q.empty():
p.next = q.get().item
return head.next
时间复杂度: O ( K n l o g K ) O(KnlogK) O(KnlogK)
空间复杂度: O ( K ) O(K) O(K)
官方给的解答:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
from queue import PriorityQueue
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
head = p = ListNode(-1)
q = PriorityQueue()
for l in lists:
if l:
q.put((l.val, l))
while not q.empty():
val, node = q.get()
listnode = ListNode(val)
p.next = listnode
p = p.next
node = node.next
if node:
q.put((node.val, node))
return head.next
会报错,报错用例:
[[1,4,5],[1,3,4],[2,6]]
报错原因:
TypeError: '<' not supported between instances of 'ListNode' and 'ListNode'
heappush(self.queue, item)
Line 233 in _put (/usr/lib/python3.8/queue.py)
self._put(item)
Line 149 in put (/usr/lib/python3.8/queue.py)
q.put((l.val, l))
Line 13 in mergeKLists (Solution.py)
ret = Solution().mergeKLists(param_1)
Line 48 in _driver (Solution.py)
_driver()
Line 59 in <module> (Solution.py)
对比下官方的代码和我的代码,就能知道问题在哪了。