Python中小集的性能

时间:2012-06-13 21:52:27

标签: python list set bitarray

我正在寻找在Python中表示给定范围(比如0-10)中的小整数集的最有效方法。在这种情况下,效率意味着快速构建(来自未排序的列表),快速查询(每组上的几个查询),以及分类版本的合理快速构造(可能每十组左右一次)。先验候选人正在使用Python的内置集类型(快速查询),使用排序数组(可能更快到构造?),或者使用位数组(如果我在C中则快速一切......但我怀疑Python会是有效(?))。有什么选择的建议吗?

感谢。

4 个答案:

答案 0 :(得分:1)

我使用位图并将“set”的成员存储在int中...在这种情况下实际上可能比内置set类型更快 - 尽管我没有测试过。它肯定需要更少的存储空间。

<强>更新

我现在没有时间做一个类似于完整集的实现,并根据Python的内置类对其进行基准测试,但我认为这是一个说明我的建议的工作示例。我认为您同意,代码看起来相当快,内存效率也很高。

鉴于Python几乎透明的“无限”长整数功能,所写的内容将自动使用比你需要的范围更大的整数值,尽管这样做可能会减慢一些。 ;)

class BitSet(object):
    def __init__(self, *bitlist):
        self._bitmap = 0
        for bitnum in bitlist:
            self._bitmap |= (1 << bitnum)

    def add(self, bitnum):
        self._bitmap |= (1 << bitnum)

    def remove(self, bitnum):
        if self._bitmap & (1 << bitnum):
            self._bitmap &= ~(1 << bitnum)
        else:
            raise KeyError

    def discard(self, bitnum):
       self._bitmap &= ~(1 << bitnum)

    def clear(self):
        self._bitmap = 0

    def __contains__(self, bitnum):
        return bool(self._bitmap & (1 << bitnum))

    def __int__(self):
        return self._bitmap

if __name__ == '__main__':

    bs = BitSet()

    print '28 in bs:', 28 in bs
    print 'bs.add(28)'
    bs.add(28)
    print '28 in bs:', 28 in bs

    print
    print '5 in bs:', 5 in bs
    print 'bs.add(5)'
    bs.add(5)
    print '5 in bs:', 5 in bs

    print
    print 'bs.remove(28)'
    bs.remove(28)
    print '28 in bs:', 28 in bs

答案 1 :(得分:0)

我的建议是坚持使用内置的set()。编写胜过内置C代码以提高性能的Python代码将非常困难。如果您依赖内置的C代码,构建速度和查找速度将是最快的。

对于排序列表,最好的办法是使用内置排序功能:

x = set(seq) # build set from some sequence
lst = sorted(x)  # get sorted list from set

通常,在Python中,编写的代码越少,它就越快。你越依赖Python的内置C基础,速度就越快。在许多情况下,解释的Python比C代码慢20到100倍,而且如果你想要提前使用内置功能,就很难如此聪明。

如果你的集合保证总是在[0,10]范围内的整数,并且你想确保内存占用量尽可能小,那么整数内的位标志就是你要走的路。

pow2 = [2**i for i in range(32)]

x = 0  # set with no values
def add_to_int_set(x, n):
    return x | pow2[n]

def in_int_set(x, n):
    return x & pow2[n]

def list_from_int_set(x):
    return [i for i in range(32) if x & pow2[i]]

我打赌这实际上比使用内置的set()函数慢,但是你知道每个集合只是一个int对象:4个字节,加上开销一个Python对象。

如果你真的需要数十亿,你可以使用NumPy array而不是Python列表来节省空间; NumPy array只会存储裸整数。事实上,NumPy有一个16位整数类型,所以如果你的集合实际上只在[0,10]的范围内,你可以使用NumPy array将存储大小减少到两个字节。

http://www.scipy.org/FAQ#head-16a621f03792969969e44df8a9eb360918ce9613

答案 2 :(得分:0)

在这种情况下,您可能只使用True / False值列表。 set使用的哈希表将执行相同的操作,但它将包括散列,存储桶分配和冲突检测的开销。

myset = [False] * 11
for i in values:
    myset[i] = True
mysorted = [i for i in range(11) if myset[i]]

一如既往,您需要自己计时,了解它在您的环境中是如何运作的。

答案 3 :(得分:0)

即使对于小型馆藏,“包含”支票的套票速度也相当快。

>>> Timer("3 in values", 'values = [range(10)]').timeit(number = 10**7)
0.5200109481811523
>>> Timer("3 in values", 'values = set(range(10))').timeit(number = 10**7)
0.2755239009857178

另一方面,正如您所指出的,构建集合需要更长的时间。

>>> Timer("set(range(10))").timeit(number = 10**7)
5.87517786026001
>>> Timer("list(range(10))").timeit(number = 10**7)
4.129410028457642

排序时也有一些区别:

>>> Timer("sorted(values)", 'values = set(range(10, 0, -1))').timeit(number = 10**7)
5.277467966079712
>>> Timer("sorted(values)", 'values = list(range(10, 0, -1))').timeit(number = 10**7)
4.3836448192596436
>>> Timer("values.sort()", 'values = list(range(10, 0, -1))').timeit(number = 10**7)
2.073429822921753

就地排序的速度明显更快,并且仅适用于列表。

因此,如果您每个集合只进行少量查询,则列表的性能更高。在执行大量查询时,我会使用集合。
无论哪种情况,小收藏之间的差异都很小。

不建议在Python中构建自己的集合类型以获得更好的性能。