Python: Zope的BTree OOSet、IISet等... 适合这个需求吗?

0 投票
2 回答
920 浏览
提问于 2025-04-15 13:09

我问了另一个问题:https://stackoverflow.com/questions/1180240/best-way-to-sort-1m-records-in-python,我想找出排序100万条记录的最佳方法。在我的情况下,我需要能够向集合中添加额外的项目,并让它们重新排序。有人建议我尝试使用Zope的BTrees来完成这个任务。看了一些资料后,我对应该在集合中放什么数据有点困惑。

简单来说,对于每条记录,我有两部分数据。1. 一个唯一的ID,代表一个用户;2. 一个用于排序的值。

我看到可以将这些项目作为元组添加到一个OOSet中,其中用于排序的值在索引0的位置。所以,比如说(200, 'id1'),(120, 'id2'),(400, 'id3'),结果集合会按照id2, id1和id3的顺序排序。

但是,要求之一是每个ID在集合中只能出现一次。我会定期向集合中添加额外的数据,而新数据可能会包含重复的'ids'。如果有重复的ID,我想更新它的值,而不是添加一个新的条目。所以,基于上面的元组,我可能会添加(405, 'id1'),(10, 'id4')到集合中,并希望输出的顺序是id4, id2, id3, id1

有没有什么建议可以实现这个目标?抱歉我对这个主题还不太熟悉。

* 编辑 - 额外信息 *

这里是项目中的一些实际代码:

for field in lb_fields:
        t = time.time()
        self.data[field] = [ (v[field], k) for k, v in self.foreign_keys.iteritems() ]
        self.data[field].sort(reverse=True)
        print "Added %s: %03.5f seconds" %(field, (time.time() - t))

foreign_keys是原始数据,以字典形式存储,每个ID作为键,额外数据的字典作为值。data是一个包含已排序数据列表的字典。

顺便提一下,随着每次for field in lb_fields的迭代,排序所需的时间会增加——虽然增加不多,但还是能感觉到。对每个16个字段排序100万条记录后,大约使用了4GB的内存。最终这将在一台有48GB内存的机器上运行。

2 个回答

1

解决你的问题是完全可能的。你只需要记住,Python中的容器类型总是通过调用对象的方法来比较对象。因此,你可以这样做:

class Record:
    'Combination of unique part and sort part.'
    def __init__(self, unique, sort):
        self.unique = unique
        self.sort = sort

    def __hash__(self):
        # Hash should be implemented if __eq__ is implemented.
        return hash(self.unique)

    def __eq__(self, other):
        return self.unique == other.unique

    def __lt__(self, other):
        return self.sort < other.sort

 records = btree((Record(u, s) for u, s in zip(unique_data, sort_data)))

 print(records.pop())

注意事项:

  • 根据你喜欢的容器类型的实现方式,你可能还需要添加!=、<=、>和>=这些方法。
  • 只要x.unique == y.unique等于x.sort == y.sort,这不会破坏==和<=之间的关系。
1

我觉得BTrees或者其他传统的排序数据结构(比如红黑树等)对你没有帮助,因为它们是根据键来保持顺序的,而不是根据对应的值。换句话说,它们保证唯一性的字段和排序的字段是同一个。你的需求不一样,因为你想要在一个字段上保持唯一性,而在另一个字段上保持排序。

你的性能需求是什么?我在一台不是特别快的笔记本上,用纯Python实现了一个简单的方案,基于Python字典来保证唯一性,使用Python的排序功能。对于一百万个元素的原始构建,我大约花了5秒钟(基本上是对这些元素进行排序,开始时它们是一个字典),而对于“更新”操作,添加了20,000个新的id/value对,其中一半是“重叠”的(也就是覆盖了已有的id),另一半是新的,这个过程大约花了9秒钟。我可以用更快的方法来实现更新,大约6.5秒,但这种实现有个问题:如果其中一个“新”的对和一个“旧”的对完全相同(包括id和value),就会出现重复。为了防止这种“完全相同的重复”,我的时间从6.5秒增加到了9秒,我想你也需要采取类似的预防措施。

这5秒和9秒的时间距离你的需求有多远?要考虑你将要运行的机器的实际速度,比如我用的这台2.4 GHz的Core Duo,2GB的内存,以及这台笔记本的典型性能问题。换句话说,这个时间是否接近“可以接受的范围”,值得你去调整和尝试挤出最后的几毫秒,还是说你需要快几个数量级的性能?

我尝试过几种其他的方法(用SQL数据库、用C++和它的std::sort等),但它们都更慢,所以如果你需要更高的性能,我不太确定你该怎么做。

编辑:因为提问者说这个性能是可以接受的,但他无法达到接近这个水平,我想我最好展示一下我用来测量这些时间的脚本...:

import gc
import operator
import random
import time


nk = 1000

def popcon(d):
  for x in xrange(nk*1000):
    d['id%s' % x] = random.randrange(100*1000)

def sorted_container():
  ctr = dict()
  popcon(ctr)
  start = time.time()
  ctr_sorted = ctr.items()
  ctr_sorted.sort(key=operator.itemgetter(1))
  stend = time.time()
  return stend-start, ctr_sorted

def do_update(ctr, newones):
  start = time.time()
  dicol = dict(ctr)
  ctr.extend((k,v) for (k,v) in newones if v!=dicol.get(k,None))
  dicnu = dict(newones)
  ctr.sort(key=operator.itemgetter(1))
  newctr = [(k,v) for (k,v) in ctr if v==dicnu.get(k,v)]
  stend = time.time()
  return stend-start, newctr

def main():
  random.seed(12345)
  for x in range(3):
    duration, ctr = sorted_container()
    print 'dict-to-sorted, %d: %.2f sec, len=%d' % (x, duration, len(ctr))
    newones = [('id%s' % y, random.randrange(nk*100))
                for y in xrange(nk*990,nk*1010)]
    duration, ctr = do_update(ctr, newones)
    print 'updt-to-sorted, %d: %.2f sec, len=%d' % (x, duration, len(ctr))
    del ctr
    gc.collect()

main()

这是一次典型的运行:

$ time python som.py
dict-to-sorted, 0: 5.01 sec, len=1000000
updt-to-sorted, 0: 9.78 sec, len=1010000
dict-to-sorted, 1: 5.02 sec, len=1000000
updt-to-sorted, 1: 9.12 sec, len=1010000
dict-to-sorted, 2: 5.03 sec, len=1000000
updt-to-sorted, 2: 9.12 sec, len=1010000

real    0m54.073s
user    0m52.464s
sys 0m1.258s

总的耗时比我测量的总时间多几秒,显然是因为它还包括了填充容器所需的时间、随机生成“新数据”的时间、每次运行结束时销毁和垃圾回收的时间等等。

这是在一台Macbook上,使用系统自带的Python 2.5.2,运行Mac OS X 10.5.7,2.4 GHz的Intel Core Duo和2GB的内存(当我使用不同版本的Python时,时间变化不大)。

撰写回答