Python数据结构,用于高效添加,删除和random.choice

时间:2013-04-13 22:06:38

标签: python data-structures

我正在寻找一种内置的Python数据结构,可以add一个新元素,remove一个现有元素,并选择一个随机元素,所有这些都优于O(n)时间

我希望set可以做到这一点,但是AFAIK,从Python集中选择随机元素的唯一方法是random.choice(list(my_set)),这需要花费O(n)时间。

我非常喜欢Python内置的解决方案,因为我需要高效且易于部署。不幸的是,Python似乎没有内置的树数据类型。

2 个答案:

答案 0 :(得分:8)

Python没有内置数据结构,可以满足您的所有3个要求。

也就是说,自己实现一棵树是相当简单的。


另一种选择是将字典与列表结合起来创建实际上是一个集合,同时维护其项目列表:

import random

class ListDict(object):
    def __init__(self):
        self.item_to_position = {}
        self.items = []

    def add_item(self, item):
        if item in self.item_to_position:
            return
        self.items.append(item)
        self.item_to_position[item] = len(self.items)-1

    def remove_item(self, item):
        position = self.item_to_position.pop(item)
        last_item = self.items.pop()
        if position != len(self.items):
            self.items[position] = last_item
            self.item_to_position[last_item] = position

    def choose_random_item(self):
        return random.choice(self.items)

由于列表上的唯一操作是.pop().append(),因此它们不应超过常量时间(至少在大多数Python实现中)。

答案 1 :(得分:0)

一棵树似乎过大了。除非我缺少某些内容,否则您可以对O(1)中的所有操作使用dict。这只是概念证明;您可以将dict或set设置为子类,并执行更完整的实现。

import random
from time import perf_counter

class SetFastRnd:
    def __init__(self, it):
        self.elems = {k: k for k in it}
        
    def __contains__(self, elem):
        return elem in self.elems

    def __iter__(self):
        return iter(self.elems)

    def __len__(self):
        return len(self.elems)

    def __getitem__(self, elem):
        return self.elems[elem]
        
    def __repr__(self):
        return str(self.elems.keys())
        
    def add(self, elem):
        self.elems[elem] = elem

    def remove(self, elem):
        del self.elems[elem]
    
if __name__ == "__main__":
    n = 100000000
    s = SetFastRnd(range(n))
    start_time = perf_counter()
    print(n // 2 in s) # True
    s.remove(42)
    print(42 in s)     # False
    s.add(42)
    print(42 in s)     # True
    random.choice(s)   # O(1)
    random.choices(s, k=5) # O(1)
    print("with dict:", perf_counter() - start_time)
    s = set(range(n))
    start_time = perf_counter()
    random.sample(s, 1) # O(n)
    print("with set: ", perf_counter() - start_time)

输出:

True
False
True
with dict: 0.0043047999999998865
with set:  0.5654710000000005