python3的OrderedDict
Posted langb2014
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python3的OrderedDict相关的知识,希望对你有一定的参考价值。
OrderedDict 是 collections 提供的一种数据结构, 它提供了有序的dict结构。先把源代码贴一下:
class OrderedDict(dict):
'记住插入顺序的字典'
# 一个继承自dict的键值对字典
# 继承的字典提供 __getitem__, __len__, __contains__, get 方法
# 所有方法的O() 均与正常的字典一样.
# 内部的 self.__map 字典将key与双向链表的指针关联在一起
# 循环双向链表是以一个哨兵元素作为开始和结束节点的
# 哨兵元素永远不会被删除 (这简化了算法).
def __init__(*args, **kwds):
'''初始化一个有序字典。 方法的签名与常规字典一样'''
'''Initialize an ordered dictionary. The signature is the same as
regular dictionaries, 但是并不建议使用关键字参数进行初始化,因为他们的插入顺序是任意的,没有保证的
'''
if not args:
raise TypeError("descriptor '__init__' of 'OrderedDict' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
self.__root = root = [] # sentinel node
root[:] = [root, root, None]
self.__map =
self.__update(*args, **kwds)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# 删除已有的元素,使用 self.__map 来获取key对应的链表
# 我们通过更新该节点的前继节点和后续节点中的链接来删除
dict_delitem(self, key)
link_prev, link_next, _ = self.__map.pop(key)
link_prev[1] = link_next # update link_prev[NEXT]
link_next[0] = link_prev # update link_next[PREV]
def __iter__(self):
'od.__iter__() <==> iter(od)'
# 按顺序遍历链表
root = self.__root
curr = root[1] # start at the first node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[1] # move to next node
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
# 反向遍历链表
root = self.__root
curr = root[0] # start at the last node
while curr is not root:
yield curr[2] # yield the curr[KEY]
curr = curr[0] # move to previous node
def clear(self):
'od.clear() -> None. 删除所有的链表中的元素'
root = self.__root
root[:] = [root, root, None]
self.__map.clear()
dict.clear(self)
然后看一下核心结构的内存示意图:
这里展示了递归多维数组的内存结构
每个链接都存储为长度为3的序列 : [PREV, NEXT, KEY]
od的有序实际上是由一个双向链表实现的. 由于 Python 里面的list是可变对象, 一个节点list里的 PREV 和 NEXT 是对前驱和后继节点list的引用
在初始化中, 前驱和后继都指向本身节点,方便接下来实现环链
核心代码:
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
# 添加新元素的时候会在链表的末尾创建新链表, 并使用新的 键值对 更新被继承的字典
if key not in self:
root = self.__root
# 这个root 就是所谓的哨兵节点
last = root[0] # root 节点的前驱节点
last[1] = root[0] = self.__map[key] = [last, root, key]
# 将root节点作为尾节点,创建新节点,并更新root节点的前驱节点的后继节点和root节点的前驱节点的指向, 形象一点就是在root 节点前不断的对新节点进行前插(基本链表算法)
return dict_setitem(self, key, value)
为什么不简单使用list来进行保存, 而是要使用这种结构的双向链表?这就涉及到了链表和数组的主要用途. 两者同样是序列,数组按照 index 取值, 对于固定的静态序列数据的存取都是 O(1), 双向链表 按照 pre, next 遍历, 因为节点是可变对象, 可以被引用(对于 od来说就是 self.__map[key]的用途), 对于 动态 的序列存取也是 O(1)。
od显然要维护一个动态序列, 所以链表就是一个非常好的选择。你可能想到list可以del某个元素, 但是这其实破坏了数组的规则, index已被改变, 无法按照原有的index进行存取。需要移动大量数组元素。
summary:
数组静态分配内存,链表动态分配内存
数组在内存中连续,链表不连续
数组元素在栈区,链表元素在堆区
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n)
这里使用__map来进行定位,所以复杂度也是O(1)
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
用例:
OrderedDict是有序的,有序体现在,先添加的排在前面,后添加的排在后面
1 2 3 4 5 6 7 |
|
popitem 弹出最后一个组
1 2 3 4 5 6 7 8 9 10 |
|
pop弹出指定元素,必须写参数
1 2 3 4 5 6 7 8 9 10 |
|
move_to_end将指定元素移至最后
1 2 3 4 5 6 7 8 9 10 |
|
以上是关于python3的OrderedDict的主要内容,如果未能解决你的问题,请参考以下文章
Python3中collections.OrderedDict介绍
python实现字典遍历稳定有序使用collection包OrderedDict