繁体   English   中英

就地排序字典

[英]Sort dict in-place

你如何就地对字典进行排序? 它没有像list.sort这样的排序方法吗?

d = {3: 'three', 1: 'one', 2: 'two'}
tmp = dict(sorted(d.items()))
d.clear()
d.update(tmp)

想要这样的结果 ^ 但应该是适当的,即不使用双 memory。 和其他引用相同的 object 应该看到重新订购!

没有办法做到这一点。 字典现在是订单保留,但只是保留 - 它们并非旨在支持复杂的订单操作操作。

导致顺序保留字典的字典实现更改从根本上说在它可以支持的顺序操作类型方面非常有限。 dict 的 hash 表将索引存储到一个密集的条目数组中,正是这个数组维护了 dict 的元素顺序。

在不使 hash 表失效的情况下,无法对密集数组进行任意重新排序。 为了解决冲突,即使删除条目也必须在其位置留下一个虚拟标记,并且如果没有完整的 hash 表重建,则该条目的位置不能重复使用。

即使您尝试通过删除和替换条目来执行某种低效的手动排序,您也会积累假人并触发 hash 表重建,从而消耗您不想使用的额外 memory。 这是一个快速而肮脏的演示:

import os

os.system(f'grep VmPeak /proc/{os.getpid()}/status')

x = dict.fromkeys(range(2**16))

os.system(f'grep VmPeak /proc/{os.getpid()}/status')

for i in range(2**16):
    if i == 21845:
        os.system(f'grep VmPeak /proc/{os.getpid()}/status')
    k = next(iter(x))
    x[k] = x.pop(k)
    if i == 21845:
        os.system(f'grep VmPeak /proc/{os.getpid()}/status')

os.system(f'grep VmPeak /proc/{os.getpid()}/status')

Output:

VmPeak:    15092 kB
VmPeak:    20224 kB
VmPeak:    20224 kB
VmPeak:    24832 kB
VmPeak:    24832 kB

我们使用已经排序的字典并重复提取顺序中的第一个键并将其放置在顺序的末尾,以匹配访问模式,而不是实际排序将执行删除和替换排序来对此字典进行排序。 当我们达到 dict 重建的阈值时,由于需要分配 dict 内部数据结构的第二个副本,峰值 memory 使用量立即跳跃。

从技术上讲,您可以对键进行排序,然后pop重新插入值。 这样,对相同 object 的其他引用也会看到更改。 但是,它不会使您免于额外的 memory 使用。

for key in sorted(d):
    d[key] = d.pop(key)

根据 dict 的负载因子,这可能会触发相当多的 hash 表重建,因此会减慢计算速度,如下例所示:

In [1]: def inplace(d): 
   ...:     for key in sorted(d): 
   ...:         d[key] = d.pop(key) 
   ...:                                                                                       

In [2]: def create_new(d): 
   ...:     return dict(sorted(d.items())) 
   ...:                                                                                       

In [3]: import math                                                                           

In [4]: limit = (2/3) * 2**20                                                                

In [5]: load_factor_low = {i: i for i in range(math.ceil(limit) + 1)}                        

In [6]: load_factor_high = {i: i for i in range(math.floor(limit) - 1)}                      

In [7]: %timeit create_new(load_factor_low)                                                         
116 ms ± 1.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]: %timeit inplace(load_factor_low)                                                     
104 ms ± 2.19 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit create_new(load_factor_high)                                                        
89.8 ms ± 1.45 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [10]: %timeit inplace(load_factor_high)                                                    
128 ms ± 1.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM