[英]What is the fastest way to check for existence and get random element from data structure in python?
我需要一个允许在 O(1) 中进行存在检查以及在 O(1) 中选择随机元素的数据结构(最好是内置的 python 类型)。 要存储在结构中的元素是唯一的整数。 我已经考虑过这个选项:
我需要实现复合数据结构吗? 最好的方法是什么? 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.