在Python中对1M记录进行排序的最佳方法

时间:2009-07-24 21:25:24

标签: python

我有一个运行的服务,它包含大约1,000,000个字典的列表并执行以下操作

myHashTable = {}
myLists = { 'hits':{}, 'misses':{}, 'total':{} }
sorted = { 'hits':[], 'misses':[], 'total':[] }
for item in myList:
  id = item.pop('id')
  myHashTable[id] = item
  for k, v in item.iteritems():
    myLists[k][id] = v

所以,如果我有以下词典列表:

[ {'id':'id1', 'hits':200, 'misses':300, 'total':400},
  {'id':'id2', 'hits':300, 'misses':100, 'total':500},
  {'id':'id3', 'hits':100, 'misses':400, 'total':600}
]

我最终得到了

myHashTable =
{ 
  'id1': {'hits':200, 'misses':300, 'total':400},
  'id2': {'hits':300, 'misses':100, 'total':500},
  'id3': {'hits':100, 'misses':400, 'total':600}
}

myLists = 

    {
      'hits': {'id1':200, 'id2':300, 'id3':100},
      'misses': {'id1':300, 'id2':100, 'id3':400},
      'total': {'id1':400, 'id2':500, 'id3':600}
    }

然后我需要对每个myLists词典中的所有数据进行排序。

我目前正在做的事情如下:

def doSort(key):
  sorted[key] = sorted(myLists[key].items(), key=operator.itemgetter(1), reverse=True)

which would yield, in the case of misses:
[('id3', 400), ('id1', 300), ('id2', 200)] 

当我有多达100,000条记录时,这很有用,但是有1,000,000条,至少需要5到10分钟才能对每个记录进行排序,总共16条(我原来的字典列表实际上有17个字段,包括id,这是弹出)

  

*编辑* 此服务是一个ThreadingTCPServer,它有一个方法   允许客户端连接和添加   新数据。新数据可能包括   新记录(意思是字典   已经存在的独特'id'   记忆)或修改记录(意思是   具有不同数据的相同'id'   其他关键值对

     

所以,一旦这个运行,我会通过   在

[
  {'id':'id1', 'hits':205, 'misses':305, 'total':480},
  {'id':'id4', 'hits':30, 'misses':40, 'total':60},
  {'id':'id5', 'hits':50, 'misses':90, 'total':20
]
     

我一直在使用字典   存储数据,以便我不会结束   有重复。之后   字典更新了   每个新的/修改过的数据   它们。

     

* END EDIT *

那么,对我进行排序的最佳方式是什么?有更好的方法吗?

11 个答案:

答案 0 :(得分:13)

你可以从Guido找到这个相关的答案:Sorting a million 32-bit integers in 2MB of RAM using Python

答案 1 :(得分:4)

你真正想要的是一个有序的容器,而不是一个无序的容器。这会在插入结果时隐式排序。此标准数据结构是树。

然而,在Python中似乎没有其中一个。我无法解释;这是任何语言的核心基础数据类型。 Python的dict和set都是无序容器,它们映射到哈希表的基本数据结构。它应该有一个优化的树数据结构;你可以用很多东西做一些哈希表是不可能做到的,并且它们很难实现,因此人们通常不希望自己这样做。

(也没有映射到链表,也不应该是核心数据类型。不,deque不等同。)

我没有一个现有的有序容器实现指向您(它可能应该是本机实现的,而不是Python中实现的),但希望这会指向正确的方向。

良好的树实现应支持按值迭代范围(“按顺序迭代[2,100]中的所有值”),从O(1)中的任何其他节点查找next / prev值,进行有效范围提取(“删除[2,100]中的所有值,并将它们返回到新的树“)等。如果有人对Python有这样一个优化的数据结构,我很想知道它。 (并非所有操作都适合Python的数据模型;例如,要从另一个值获取next / prev值,您需要引用节点,而不是值本身。)

答案 2 :(得分:1)

如果您有固定数量的字段,请使用元组而不是字典。将要排序的字段放在第一个位置,然后使用mylist.sort()

答案 3 :(得分:1)

其他人提供了一些很好的建议,试试看。

作为一般建议,在这种情况下,您需要分析您的代码。确切知道大部分时间花在哪里。在你最不希望的地方,瓶颈隐藏得很好 如果涉及到大量的数字运算,那么像(现在已经死亡的)psyco这样的JIT编译器也可能有所帮助。当处理需要几分钟或几小时时,加速真的非常重要。

答案 4 :(得分:1)

这似乎很快。

raw= [ {'id':'id1', 'hits':200, 'misses':300, 'total':400},
    {'id':'id2', 'hits':300, 'misses':100, 'total':500},
    {'id':'id3', 'hits':100, 'misses':400, 'total':600}
]

hits= [ (r['hits'],r['id']) for r in raw ]
hits.sort()

misses = [ (r['misses'],r['id']) for r in raw ]
misses.sort()

total = [ (r['total'],r['id']) for r in raw ]
total.sort()

是的,它会对原始数据进行三次传递。我认为这比通过一次传递数据更快。

答案 5 :(得分:1)

不是试图保持列表的顺序,也许你可以使用堆队列。它允许您推送任何项目,将“最小”项目保持在h[0],然后弹出此项目(并“冒泡”下一个最小项目)是O(nlogn)操作。

所以,问问自己:

  • 我是否需要一直订购整个清单? :使用有序结构(如Zal的BTree包,Ealdwulf为mentioned

  • 或订购的整个清单,但仅在一天的随机插入工作之后?:使用类似于您正在进行的操作,或者像S.Lott's answer

  • 或者只是一些“最小”的物品? :使用heapq

答案 6 :(得分:0)

sorted(myLists[key], key=mylists[key].get, reverse=True)

应该为你节省一些时间,但不是很多。

答案 7 :(得分:0)

我会研究使用不同的排序算法。像Merge Sort这样的东西可能会起作用。将列表分成较小的列表并单独排序。然后循环。

伪代码:

list1 = []  // sorted separately
list2 = []  // sorted separately

// Recombine sorted lists
result = []
while (list1.hasMoreElements || list2.hasMoreElements):
   if (! list1.hasMoreElements):
       result.addAll(list2)
       break
   elseif (! list2.hasMoreElements):
       result.AddAll(list1)
       break

   if (list1.peek < list2.peek):
      result.add(list1.pop)
   else:
      result.add(list2.pop)

答案 8 :(得分:0)

Glenn Maynard是正确的,这里排序的映射是合适的。这是python的一个:http://wiki.zope.org/ZODB/guide/node6.html#SECTION000630000000000000000

答案 9 :(得分:0)

我已经对原始方式和SLott的提案进行了一些快速分析。在这两种情况下,每个场都需要5-10分钟。实际的排序不是问题。看起来大部分时间花费在投放数据和转换数据上。此外,我的内存使用率暴涨 - 我的python超过350兆内存!你确定你没有用完所有ram并分页到磁盘吗?即使使用我的3年老式省电处理器笔记本电脑,我看到每个键的结果不到5-10分钟就有100万件。我无法解释的是实际sort()调用的可变性。我知道python sort在排序部分排序列表时非常好,所以也许他的列表在从原始数据到要排序的列表的转换中得到部分排序。

以下是slott方法的结果:

done creating data
done transform.  elapsed: 16.5160000324
sorting one key slott's way takes 1.29699993134

这是获得这些结果的代码:

starttransform = time.time()
hits= [ (r['hits'],r['id']) for r in myList ]
endtransform = time.time()
print "done transform.  elapsed: " + str(endtransform - starttransform)
hits.sort()
endslottsort = time.time()
print "sorting one key slott's way takes " + str(endslottsort - endtransform)

现在是原始方法的结果,或者至少是添加了一些检测的关闭版本:

done creating data
done transform.  elapsed: 8.125
about to get stuff to be sorted 
done getting data. elapsed time: 37.5939998627
about to sort key hits
done  sorting on key <hits> elapsed time: 5.54699993134

以下是代码:

for k, v in myLists.iteritems():
    time1 = time.time()
    print "about to get stuff to be sorted "
    tobesorted = myLists[k].items()
    time2 = time.time()
    print "done getting data. elapsed time: " + str(time2-time1)
    print "about to sort key " + str(k) 
    mysorted[k] = tobesorted.sort( key=itemgetter(1))
    time3 = time.time()
    print "done  sorting on key <" + str(k) + "> elapsed time: " + str(time3-time2)

答案 10 :(得分:-4)

老实说,最好的方法是不使用Python。如果性能是一个主要问题,请使用更快的语言。