collections.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)

然后看一下核心结构的内存示意图:

image

这里展示了递归多维数组的内存结构

每个链接都存储为长度为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)

ok, 为什么不简单使用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)。

本文章首发在 PythonCaff