【问题标题】:Fastest way to sort a python 3.7+ dictionary对 python 3.7+ 字典进行排序的最快方法
【发布时间】:2018-11-02 18:23:02
【问题描述】:

既然 insertion order of Python dictionaries is guaranteed 从 Python 3.7(和 in CPython 3.6)开始,那么对字典进行排序的最佳/最快方法是什么?无论是按值还是按键?

最明显的方法可能是这样的:

by_key = {k: dct[k] for k in sorted(dct.keys())}
by_value = {k: dct[k] for k in sorted(dct.keys(), key=dct.__getitem__)}

是否有其他更快的方法可以做到这一点?

请注意,这个问题不是重复的,因为以前关于如何对字典进行排序的问题已经过时了(基本上,答案是,你不能;改用collections.OrderedDict)。

【问题讨论】:

  • 这相当于分析同一代码的一堆版本。就像,当您可以使用{k: v 并使用items() 代替keys() 时,为什么偏爱{k: dct[k] ...。 by 值是一样的,但以operator.itemgetter(1) 为键。
  • @gddc 我认为你所说的可能是这种情况(因此这是一个无聊的问题)但我想我还是会问,因为我可能有一种有趣的开箱即用的方式不知道。由于这是非常新的,我认为正确的成语尚未建立。
  • 公平。恕我直言,我只是等待社区将排序方法添加到底层字典类(现在它们已排序),我敢打赌你会看到类似def sort(byValues = False) 的东西,所以默认情况下它是按键排序的,但是通过像sort(True) 这样的调用,您可以按值(或类似的东西)排序。
  • @g.d.d.c 我希望你是对的。无法就地排序的可变有序事物感觉像是一种反模式。
  • 按键排序最少的代码是dict(sorted(dct.items())

标签: python performance dictionary python-3.7


【解决方案1】:

TL;DR:在 CPython 3.7 中按键或按值(分别)排序的最佳方法:

{k: d[k] for k in sorted(d)}
{k: v for k,v in sorted(d.items(), key=itemgetter(1))}

在带有sys.version 的macbook 上测试:

3.7.0b4 (v3.7.0b4:eb96c37699, May  2 2018, 04:13:13)
[Clang 6.0 (clang-600.0.57)]

一次性设置 1000 个浮点数:

>>> import random
>>> from operator import itemgetter
>>> random.seed(123)
>>> d = {random.random(): random.random() for i in range(1000)}

按键排序(最好到最差):

>>> %timeit {k: d[k] for k in sorted(d)}
# 296 µs ± 2.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: d[k] for k in sorted(d.keys())}
# 306 µs ± 9.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit dict(sorted(d.items(), key=itemgetter(0)))
# 345 µs ± 4.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=itemgetter(0))}
# 359 µs ± 2.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit dict(sorted(d.items(), key=lambda kv: kv[0]))
# 391 µs ± 8.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit dict(sorted(d.items()))
# 409 µs ± 9.33 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: v for k,v in sorted(d.items())}
# 420 µs ± 5.39 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=lambda kv: kv[0])}
# 432 µs ± 39.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

按值对数字进行排序(从最好到最差):

>>> %timeit {k: v for k,v in sorted(d.items(), key=itemgetter(1))}
# 355 µs ± 2.24 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit dict(sorted(d.items(), key=itemgetter(1)))
# 375 µs ± 31.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=lambda kv: kv[1])}
# 393 µs ± 1.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit dict(sorted(d.items(), key=lambda kv: kv[1]))
# 402 µs ± 9.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: d[k] for k in sorted(d, key=d.get)}
# 404 µs ± 3.55 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: d[k] for k in sorted(d, key=d.__getitem__)}
# 404 µs ± 20.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit {k: d[k] for k in sorted(d, key=lambda k: d[k])}
# 480 µs ± 12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

一次性设置大量字符串:

>>> import random
>>> from pathlib import Path
>>> from operator import itemgetter
>>> random.seed(456)
>>> words = Path('/usr/share/dict/words').read_text().splitlines()
>>> random.shuffle(words)
>>> keys = words.copy()
>>> random.shuffle(words)
>>> values = words.copy()
>>> d = dict(zip(keys, values))
>>> list(d.items())[:5]
[('ragman', 'polemoscope'),
 ('fenite', 'anaesthetically'),
 ('pycnidiophore', 'Colubridae'),
 ('propagate', 'premiss'),
 ('postponable', 'Eriglossa')]
>>> len(d)
235886

按键对字符串进行排序:

>>> %timeit {k: d[k] for k in sorted(d)}
# 387 ms ± 1.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: d[k] for k in sorted(d.keys())}
# 387 ms ± 2.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit dict(sorted(d.items(), key=itemgetter(0)))
# 461 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit dict(sorted(d.items(), key=lambda kv: kv[0]))
# 466 ms ± 2.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=itemgetter(0))}
# 488 ms ± 10.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=lambda kv: kv[0])}
# 536 ms ± 16.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit dict(sorted(d.items()))
# 661 ms ± 9.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: v for k,v in sorted(d.items())}
# 687 ms ± 5.38 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

按值对字符串进行排序:

>>> %timeit {k: v for k,v in sorted(d.items(), key=itemgetter(1))}
# 468 ms ± 5.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit dict(sorted(d.items(), key=itemgetter(1)))
# 473 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit dict(sorted(d.items(), key=lambda kv: kv[1]))
# 492 ms ± 9.06 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: v for k,v in sorted(d.items(), key=lambda kv: kv[1])}
# 496 ms ± 1.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: d[k] for k in sorted(d, key=d.__getitem__)}
# 533 ms ± 5.33 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: d[k] for k in sorted(d, key=d.get)}
# 544 ms ± 6.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit {k: d[k] for k in sorted(d, key=lambda k: d[k])}
# 566 ms ± 5.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

注意:现实世界的数据通常包含长时间运行的已排序序列,Timsort 算法可以利用这些序列。如果对 dict 进行排序在您的快速路径上,那么建议在得出有关最佳方法的任何结论之前,在您自己的平台上使用您自己的典型数据进行基准测试。我在每个 timeit 结果前添加了一个注释字符 (#),以便 IPython 用户可以复制/粘贴整个代码块以在他们自己的平台上重新运行所有测试。

【讨论】:

  • 我在按键排序数字时始终得到相似的结果,但按值对数字排序的结果不同。
  • 非常好的时序分析。所以一些关键的观察似乎是:dict 比 dict-comprehension 更快,但是在元组上打破平局比使用 key-function 仅比较 key 成本更高,为此,使用 itemgetter 更快拉姆达。
  • (也就是说,仔细观察,特别是按值排序,dict 似乎比 dict-coprehension 慢......)我认为这真的会受益于某种视觉/表格概览。
  • 我看的时间越长,它的意义就越小……使用itemgetterdict 和 dict-comp 之间的差异是 15µs,其他都一样,但使用 @ 987654336@ 是 40µs。并且按值排序,dict 比 dict-comp 等价物。不过,在这里得到类似的结果。你知道对此有什么解释吗?
  • 时间看起来非常相似,毫无疑问会根据数据的具体情况和用于测试的系统而有所不同,那么 TL;DR 的结论是否合理? dict(sorted(d.items())) 感觉更符合 IMO 的习惯
猜你喜欢
  • 1970-01-01
  • 2021-08-14
  • 1970-01-01
  • 2014-03-06
  • 2017-07-12
  • 2023-04-05
  • 1970-01-01
  • 2015-10-24
  • 1970-01-01
相关资源
最近更新 更多