【问题标题】:Retrieve an arbitrary key from python3 dict in O(1) time在 O(1) 时间内从 python3 dict 中检索任意键
【发布时间】:2020-09-22 10:22:22
【问题描述】:

我需要从 python 字典对象中检索任意键。假设我有一本字典d。以下代码的时间复杂度是多少?

k = next(iter(d.keys()))

我知道 d.key() 在 Python 3 中的时间为 O(1),next() 的时间为 O(1)。 iter() 会发生什么? 这可以在 O(1) 时间内完成而不使用额外的空间吗?谢谢!

【问题讨论】:

  • 查看this链接
  • d.popitem() 会为您工作吗?具有从字典中删除键的副作用。
  • @jasonharper 不,我无法删除任何密钥。
  • @snatchysquid 检查过,但仍有疑问。现在是 O(n) 时间?
  • 是的,它是 O(1)

标签: python python-3.x iterator


【解决方案1】:

使用iter(或其他逻辑,例如生成器表达式,声明委托给dict的生成器函数,使用islice等)都是某种形式的包装器,它添加了.__next__()方法和一些位置簿记到对象next() 的视图上运行。

这些在很大程度上有效,因为dicts are iterable,但没有.__next__() method,所以iter 等。正在调用__iter__ 方法,该方法返回一个迭代确实具有__next__ 方法并且是view into the dict

每个 case 都只是一个 O(1) 调用的包装器,因此一旦声明,它们都将在 O(1) 时间内运行

https://wiki.python.org/moin/TimeComplexity


这是一个演示

首先创建一个相当大的字典(在慢速系统上这可能需要一段时间)

>>> from uuid import uuid4
>>> d = {str(uuid4()):str(uuid4()) for _ in range(1000000)}

显示这可以直接从现有方法完成

>>> next(d.__iter__()
'1273a529-d406-4076-8acc-8993f2613ad4'
>>> type(d.__iter__())
<class 'dict_keyiterator'>

其他对象

>>> n1 = iter(d)          # iter function
>>> n2 = (k for k in d)   # generator expression
>>> def y():              # generator delegation
...   yield from d
...
>>> import itertools
>>> i = itertools.islice(d, 1)  # slice one entry from iterable
>>> type(n1)
<class 'dict_keyiterator'>
>>> type(n2)
<class 'generator'>
>>> type(y())
<class 'generator'>
>>> type(i)
<class 'itertools.islice'>

每一个都可以用来读取第一个键

>>> next(n1)
'1273a529-d406-4076-8acc-8993f2613ad4'
>>> next(n2)
'1273a529-d406-4076-8acc-8993f2613ad4'
>>> next(y())
'1273a529-d406-4076-8acc-8993f2613ad4'
>>> next(i)
'1273a529-d406-4076-8acc-8993f2613ad4'

证明这些都提供了下一个方法

>>> dir(d)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
>>> "__next__" in dir(d)
False
>>> "__next__" in dir(n1)
True
>>> "__next__" in dir(n2)
True
>>> "__next__" in dir(y())
True
>>> "__next__" in dir(i)
True

最后,如果需要前 N 个值(而不仅仅是来自 next() 的第一个值),它们也可以在循环中有效地调用,直到达到某个长度或被 islice from itertools 切片,但会产生一些进一步的开销当形成一个列表等时

>>> import itertools
>>> list(itertools.islice(d, 5))
['1273a529-d406-4076-8acc-8993f2613ad4', 'a920460d-a193-455c-979c-a91fd700f927', 'aeccb371-43d1-4690-8aaa-d6de0cbe3801', '9aaf2a96-9ef4-4610-8723-8401008e190a', 'e4b450aa-50a2-409a-a5b2-ab88285eb770']
>>> list(itertools.islice(y(), 5))
['1273a529-d406-4076-8acc-8993f2613ad4', 'a920460d-a193-455c-979c-a91fd700f927', 'aeccb371-43d1-4690-8aaa-d6de0cbe3801', '9aaf2a96-9ef4-4610-8723-8401008e190a', 'e4b450aa-50a2-409a-a5b2-ab88285eb770']
>>> list(itertools.islice(n1, 5))
['1273a529-d406-4076-8acc-8993f2613ad4', 'a920460d-a193-455c-979c-a91fd700f927', 'aeccb371-43d1-4690-8aaa-d6de0cbe3801', '9aaf2a96-9ef4-4610-8723-8401008e190a', 'e4b450aa-50a2-409a-a5b2-ab88285eb770']
>>> list(itertools.islice(n2, 5))
['1273a529-d406-4076-8acc-8993f2613ad4', 'a920460d-a193-455c-979c-a91fd700f927', 'aeccb371-43d1-4690-8aaa-d6de0cbe3801', '9aaf2a96-9ef4-4610-8723-8401008e190a', 'e4b450aa-50a2-409a-a5b2-ab88285eb770']

另见Python iter() time complexity?snachysquid对您的回答的第一条评论)

【讨论】:

  • 谢谢,我的问题有答案了吗?
  • @Luke 每个 case 都只是一个 O(1) 调用的包装器,因此一旦声明,这些都将在 O(1) 时间内运行
  • 哦,我明白了 - 我会将扩展答案放在比第一条评论(stackoverflow.com/q/46626669/4541045)中链接的更相关的帖子(你的!)上,但没有进一步链接它们
  • @Luke 已修复!如果我的答案可以更清楚,请提出修改建议!
  • iter 不是生成器表达式,它返回一个迭代器。该迭代器可能是一个生成器,但在这种情况下,它不是。
猜你喜欢
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
  • 2012-07-17
  • 1970-01-01
  • 1970-01-01
  • 2018-07-14
  • 2019-01-19
  • 1970-01-01
相关资源
最近更新 更多