在Python 3.6+;有效地

在Python 3.6+;有效地,python,python-3.x,dictionary,python-internals,Python,Python 3.x,Dictionary,Python Internals,我知道字典在3.6中是一个实现细节,在3.7+中是正式的 考虑到它们是按顺序排列的,似乎奇怪的是,没有任何方法可以按插入顺序检索字典的第i项。可用资源似乎具有O(n)复杂性,或者: 通过O(n)进程转换为列表,然后使用list.\uuu getitem\uuu 枚举循环中的字典项,并在达到所需索引时返回值。同样,时间复杂度为O(n) 由于从列表中获取项目的复杂性为O(1),那么有没有一种方法可以实现与字典相同的复杂性?使用常规的dict或collections.OrderedDict都可以 如果

我知道字典在3.6中是一个实现细节,在3.7+中是正式的

考虑到它们是按顺序排列的,似乎奇怪的是,没有任何方法可以按插入顺序检索字典的第i项。可用资源似乎具有O(n)复杂性,或者:

  • 通过O(n)进程转换为列表,然后使用
    list.\uuu getitem\uuu
  • 枚举循环中的
    字典项,并在达到所需索引时返回值。同样,时间复杂度为O(n)
  • 由于从
    列表中获取项目的复杂性为O(1),那么有没有一种方法可以实现与字典相同的复杂性?使用常规的
    dict
    collections.OrderedDict
    都可以


    如果不可能,是否有结构性原因阻止了这种方法,或者这只是一个尚未考虑/实施的功能?

    对于
    订购的ICT
    它本质上是
    O(n)
    ,因为订购记录在

    对于内置dict,有一个向量(一个连续数组)而不是一个链表,但最终几乎是一样的:向量包含几种“虚拟对象”,即特殊的内部值,表示“这里还没有存储密钥”或“以前存储在这里但现在不再存储密钥”。这使得,例如,删除一个键非常便宜(只需用一个伪值覆盖该键)

    但是如果不在上面添加辅助数据结构,就无法跳过这些假人而不一次一个地跳过它们。因为Python使用一种开放寻址形式来解决冲突,并将负载因子保持在2/3以下,所以至少有三分之一的向量条目是虚拟的
    向量[i]
    可以在
    O(1)
    时间内访问,但实际上与第i个非伪条目没有可预测的关系。

    根据,在O(1)时间内无法按位置访问字典项有结构性原因

    如果您正在寻找按键或位置进行O(1)查找,那么值得考虑其他方法。有第三方库(如NumPy/Pandas)提供了此类功能,特别是对于不需要指针的数字数组来说更为有效

    使用Pandas,您可以构建一个具有唯一标签的“字典式”系列,通过“标签”或位置提供O(1)查找。删除标签时牺牲的是性能,这会产生O(n)成本,很像
    list

    import pandas as pd
    
    s = pd.Series(list(range(n)))
    
    # O(n) item deletion
    del s[i]
    s.drop(i)
    s.pop(i)
    
    # O(1) lookup by label
    s.loc[i]
    s.at[i]
    s.get(i)
    s[i]
    
    # O(1) lookup by position
    s.iloc[i]
    s.iat[i]
    
    pd.Series
    绝不是
    dict
    的替代品。例如,如果序列主要用作映射,则不会防止重复键,并且会导致问题。然而,当数据存储在一个连续的内存块中时,如上面的示例所示,您可能会看到显著的性能改进

    另见:


  • 它被实现为一个链表。否则,就不可能删除元素。我可以想出一个模糊的原因。它使JSON行更加稳定,没有封闭的列表和单独的字典。除了那个很小的细节,我还没有真正理解hype@o11c根据图,只有一个数组
    dk_条目
    按插入顺序排列条目。没有链接列表。删除的条目被虚拟项替换,添加新条目时,可能会调整数组的大小(删除虚拟项)。@o11c它不是作为链接列表实现的。我想他们只是不想将类似序列的行为添加到基本映射中。IOW:您不应该像列表一样使用dict,但它确实保持了顺序。根据我对>3.6实现的理解,有两个向量,稀疏索引数组是开放寻址发生的地方,但实际的条目向量只是,一个有序的条目数组,没有假人,没有?@juanpa.arrivillaga,它更复杂-什么不是?;-)封面下有“拆分”和“非拆分”的dict等。对于常规的旧dict(“非拆分”),删除一个键也会将相应的值槽设置为NULL,因此相同的事情;您必须一次跳过一个空值。请参阅dictobject.c的dicter\u iternextkey()
    中的循环。对“键”的迭代实际上是对值的迭代,这些值按插入顺序排列,但可以在任意位置包含空值。一旦找到一个none NULL值,它就包含一个指向键的指针。啊,我明白了。为了确保我正确理解您的意思,当您删除一个键时,它实际上在entries向量中被设置为
    null
    。这与POC实现不同,在POC实现中,值只是从向量中弹出(列表
    self.entries
    \uuuu delitem\uuu
    中)?我认为动机不是因为删除而招致O(N)惩罚?您链接到的POC完全是为了其他目的:更节省空间的dict实现。它根本不保留插入顺序。事实上,它的“与最后一个条目交换以避免留下“漏洞”,可以将最后一个条目移动到任何位置。当前的实现既节省空间又保持顺序,但是在保持顺序的同时不留下删除漏洞需要在被删除的条目之后实际移动每个条目。相反,它只是用NULL覆盖删除的值(留下“一个洞”)。很好。我在想,满足OP要求的最简单的数据结构是什么。@EricDuminil,是的,的确如此。在考虑dict的替代品时,人们并不总是认为“熊猫系列!”是可行的,但如果满足某些标准,那么它肯定是可行的。语法通常也是可比较的,例如
    s[i]
    s.get(i)
    dels[i]
    s.keys()
    s.items()