在Python 3中以恒定时间从字典中选择随机值?

时间:2015-09-26 22:59:49

标签: python dictionary random

我知道您可以通过多种方式从字典中选择一个随机值。

在Python 2中:

random.choice(d.keys())

在Python 3中:

random.choice(list(d.keys()))
尽管如此,两种方法都需要在随机选择之前将变换(即线性时间O(n))转换为列表。例如,我知道在Python 3中d.keys()返回一个迭代器,我猜测在Python 3中,列表是从字典内部创建的。

是否可以在恒定时间内从字典中选择一个值,即O(1)?

编辑:到目前为止的评论中,我认为这是不可能的,至少不是直截了当的。需要辅助结构。

编辑2:我认为字典可以在常量时间内随机选择,因为内部它是一个哈希表,即在内部它必须有一个数组或相似的东西。当然,这取决于内部实施,但理论上我认为这是可能的。

4 个答案:

答案 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,它同样为其关键视图提供了有效的索引。

其他人也可以使用,通常基于搜索树。