我知道您可以通过多种方式从字典中选择一个随机值。
在Python 2中:
random.choice(d.keys())
在Python 3中:
random.choice(list(d.keys()))
尽管如此,两种方法都需要在随机选择之前将变换(即线性时间O(n))转换为列表。例如,我知道在Python 3中d.keys()
返回一个迭代器,我猜测在Python 3中,列表是从字典内部创建的。
是否可以在恒定时间内从字典中选择一个值,即O(1)?
编辑:到目前为止的评论中,我认为这是不可能的,至少不是直截了当的。需要辅助结构。
编辑2:我认为字典可以在常量时间内随机选择,因为内部它是一个哈希表,即在内部它必须有一个数组或相似的东西。当然,这取决于内部实施,但理论上我认为这是可能的。
答案 0 :(得分:1)
假设不能单独使用Python import random
RNG = random.Random()
class Tracker(object):
def __init__(self):
self.free = []
self.nodes = []
def add(self,node):
if self.free:
seq_num = self.free.pop()
self.nodes[seq_num] = node
else:
seq_num = len(self.nodes)
self.nodes.append(node)
def random_node(self):
seq_num = RNG.randint(0,len(self.nodes)-1)
while self.nodes[seq_num] == None:
seq_num = RNG.randint(0,len(self.nodes)-1)
return self.nodes[seq_num],seq_num
def delete(self,seq_num):
self.nodes[seq_num] = None
self.free.append(seq_num)
def delete_random_node(self):
node,seq_num = self.random_node()
self.delete(seq_num)
return node
并且需要第二个数据结构,那么这里是一个廉价而有效的辅助数据结构,只跟踪当前节点
它只是将节点保存在列表中。为了支持删除它只是清空位置并保留另一个可用空间列表。
请注意,如果您只是随机删除节点,那么这就好了。如果要删除某些其他方法选择的节点,则需要在节点中存储序列号,以便找到要删除的序列号。
除非您遇到随机采样变慢时节点列表变为空的情况,否则效果很好。如果您需要处理这种情况,那么您需要在此时重新分配列表 - 这可以作为摊销成本,但会增加相当多的复杂性。例如,您需要从节点添加字典到序列号,并在重新分配节点列表时更新它。
collections.deque
这里可能会有一些小的优化。用.lib
替换空闲列表可能会使它更快一些,因为如果列表的大小经常变化,列表会慢一点。但这没什么大不了的。我认为你的节点列表会达到一个均衡的大小,然后变得非常有效,但你可以用Nones来填充它,以避免重复增长的启动成本。你可以做一些常见的子表达式消除。但所有这些只会产生很小的影响。
答案 1 :(得分:0)
在这种情况下,我只能想到一种(次要)优化:不要创建列表,只需得到一个随机数r
并迭代d.keys()
,直到得到r
第项。
def take_nth(sequence, n):
i = iter(sequence)
for _ in range(n):
next(i)
return next(i)
import random
rand_key = d[take_nth(d.keys(), random.randint(0, len(d)-1))]
这样可以提高性能,因为每次都不必迭代整个列表,但这仍然是一个坏主意。
如果你想在固定字典上重复进行随机选择,而不是将其密钥缓存到一个单独的列表中,并使用随机索引值对其进行索引。
<强> UPD:强>
为了总结评论中的讨论,以下具有向前/向后缓存和重用已删除项目的类可能会有所帮助:
import random
class RandomSampleDict(object):
def __init__(self):
self.data = {}
self.cache_ik = {}
self.cache_ki = {}
self.track = []
def lookup(self, key):
return self.data[key]
def set(self, key, value):
self.data[key] = value
def add(self, key, value):
self.data[key] = value
if len(self.track) == 0:
i = len(self.data) - 1
else:
i = self.track.pop()
self.cache_ik[i] = key
self.cache_ki[key] = i
def delete(self, key):
del self.data[key]
i = self.cache_ik[i]
del self.data_ik[i]
del self.data_ki[key]
self.track.append(i)
def random_sample_key(self):
key = None
while key is None:
i = random.randint(0, len(self.data))
if i in self.cache_ik:
return self.cache_ik[i]
答案 2 :(得分:0)
next(islice(d.values(),np.random.randint(0,len(d)-1),None))是我发现从dict d中选择随机值的最佳表现方法Python 3.以下讨论对此进行了解释。
某些标准库随机方法比可比较的numpy.random方法花费更多的运行时间。例如:
import numpy as np
timeit random.randint(0, 10)
100000 loops, best of 3: 2.52 µs per loop
timeit np.random.randint(0, 10)
1000000 loops, best of 3: 453 ns per loop
使用numpy.random.randint可以改善选择dict随机值的方法的运行时间:
from itertools import islice
import random
d = {1:'a',2:'b',3:'c',4:'d',5:'e',6:'f',7:'g',8:'h',9:'i',10:'j'}
timeit next(islice(d.values(),random.randint(0, len(d)-1),None))
100000 loops, best of 3: 3.58 µs per loop
timeit next(islice(d.values(),np.random.randint(0, len(d)-1),None))
100000 loops, best of 3: 1.26 µs per loop
# d[5] access time is about 25X smaller than 1.26 µs
timeit d[5]
10000000 loops, best of 3: 51.3 ns per loop
def take_nth(sequence, n):
i = iter(sequence)
for _ in range(n):
next(i)
return next(i)
timeit d[take_nth(d.keys(), random.randint(0, len(d)-1))]
100000 loops, best of 3: 5.07 µs per loop
timeit d[take_nth(d.keys(), np.random.randint(0, len(d)-1))]
100000 loops, best of 3: 2.66 µs per loop
答案 3 :(得分:0)
我认为很明显,通过标准dict
公共API无法做到这一点。
但是,dict
有几个免费替换,可以按某种排序顺序高效访问密钥。然后可以将其编入索引以获得随机元素。尽管他们的理论渐近性与dict不同,但在实践中它们通常表现得相同或更好。
来自Stutzbach Enterprises的blist包提供blist.sorteddict
,经过专门测试,与dict完全兼容。它为其关键视图提供了索引,这是对数的复杂性。它是用B + Trees实现的。
Grant Jenks中的SortedContainers包提供了sortedcontainers.SortedDict
,它同样为其关键视图提供了有效的索引。
其他人也可以使用,通常基于搜索树。