繁体   English   中英

在python中检查存在并从数据结构中获取随机元素的最快方法是什么?

[英]What is the fastest way to check for existence and get random element from data structure in python?

我需要一个允许在 O(1) 中进行存在检查以及在 O(1) 中选择随机元素的数据结构(最好是内置的 python 类型)。 要存储在结构中的元素是唯一的整数。 我已经考虑过这个选项:

  1. 列表:O(1) 随机选择与 random.choice,但 O(n) 存在与操作符。
  2. set: O(1) 使用 in 运算符检查存在性,但不能使用 random.choice 而不强制转换为元组 (O(n))
  3. 元组:O(1) 随机选择与 random.choice,但 O(n) 检查是否存在
  4. 字典:O(1) 存在性检查,但与其他选项(键和值)相比,内存使用量至少是其 2 倍,并且 random.choice 在不强制转换为列表或获取项目(假设为 O(n))的情况下不起作用操作)。

我需要实现复合数据结构吗? 最好的方法是什么? PD:顺便说一下,我正在使用 Python 3。

编辑:

数据结构是从带有范围生成器的列表推导式创建的,因此序列中的数据始终是有序的。 像这样的东西:

data=[x for x in range(n) if condition]

有几种方法可以解决这个问题。 这取决于最小化内存使用的重要性。 根据您所说的评论,没有必要添加或删除元素。

隐式集

对于既知道取数的范围,又知道包含在集合中的条件的特殊情况,也只需要进行少量的隶属度和抽样操作,则不需要构建任何数据结构。 您可以使用条件来测试成员资格,以及从中抽样的范围:

import random

# example usage:
# >>> s = ImplicitSet(range(10), lambda x: x > 4)

class ImplicitSet:
    def __init__(self, range, condition):
        self.range = range
        self.condition = condition
    def contains(self, x):
        return x in self.range and self.condition(x)
    def sample(self):
        # you'll need to change this if the condition might never be true
        while True:
            x = random.choice(range)
            if self.condition(x):
                return x

不幸的是,您不能只生成一个随机数并进行迭代,直到找到该集合的成员,因为这会使采样偏向于在它们之前具有更大“间隙”的数字。

这需要 O(1) 内存,因为您没有存储任何实际的数据结构。 contains操作需要花费任何时间来测试条件,但如果您只测试少量元素的成员资格,与测试范围内每个元素的条件的列表理解相比,它可以节省时间。

sample操作需要的尝试次数与满足范围内条件的元素的密度成反比,因此,例如,如果 25% 的元素条件为真,则平均需要四次尝试。 因此采样需要常数时间,但常数的大小取决于实际数字的分布。 同样,如果您只采样几次,那么这比必须测试范围内每个数字的条件的列表理解要好得多。

列出并设置

这种方式实现起来最简单,并且在恒定时间内提供成员资格测试(使用集合)和随机抽样(使用列表)。 但是,它将每个整数存储在两个容器而不是一个容器中,从而使用更多内存。

import random

class ListAndSet:
    def __init__(self, numbers):
        self.numbers_list = list(numbers)
        self.numbers_set = set(self.numbers_list) # numbers may be a generator
    def contains(self, x):
        return x in self.numbers_set
    def sample(self):
        return random.choice(self.numbers_list)

排序列表

这也很容易实现,它使用尽可能少的内存,除非您的数字在固定大小内,在这种情况下,排序array更好。 首先对数字进行排序需要 O(n log n) 时间,但之后,使用二分搜索进行成员资格测试需要 O(log n) 时间,而采样需要 O(1) 时间。

import bisect
import random

class SortedList:
    def __init__(self, numbers):
        self.sorted_list = sorted(numbers)
    def contains(self, x):
        index = bisect.bisect_left(self.sorted_list, x)
        return index < len(self.sorted_list) and self.sorted_list[index] == x
    def sample(self):
        return random.choice(self.sorted_list)

密集集

如果数字在某个范围内密集排列,则可以将它们存储在一个集合中,然后通过在该范围内生成随机数进行采样,直到找到该集合中的一个。 预期的尝试次数与范围内数字的密度成反比,因此,例如,如果您在大小为 4,000 的范围内有 1,000 个数字,则平均需要尝试四次。

import random

class DenseSet:
    def __init__(self, numbers):
        self.numbers_set = set(numbers)
        # you'll need to change this if the set might sometimes be empty
        self.low = min(self.numbers_set)
        self.high = max(self.numbers_set)
    def contains(self, x):
        return x in self.numbers_set
    def sample(self):
        # you'll need to change this if the set might sometimes be empty
        while True:
            x = random.randint(self.low, self.high)
            if x in self.numbers_set:
                return x

你自己的哈希集

如果 O(log n) 时间不够好,并且数字很稀疏,但是您绝对负担不起列表和集合所需的内存,那么您可以实现自己的哈希集; 您可以通过在线搜索找到示例代码。 要在 O(1) 预期时间中采样,请在基础列表中重复生成随机索引,直到找到非空槽。 这保证了样本的均匀分布。 预期的尝试次数与散列集的负载因子成反比,因此,例如,负载因子为 0.8,您平均需要 1.25 次尝试。

不幸的是,你不能只生成一个索引,然后迭代直到找到一个非空槽,因为采样会偏向于后面有更多空槽的元素。

除非你有很强的内存限制,否则我会使用一个列表和一个集合,你会保持同步。 您检查该集合是否存在,但从列表中随机选择。

class RandomChoice:
    def __init__(self, data):
        self.list = list(data)
        self.set = set(self.list)

    def add(self, element):
        if element not in self.set:
            self.list.append(element)
            self.set.add(element)

    def contains(self, element):
        return self.set.contains(element)

    def get_random(self):
        return random.choice(self.list)

当然,它比单个列表或单个集合需要两倍的存储空间,但它可以完成工作。

暂无
暂无

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

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