【问题标题】:Best way to sort 1M records in Python在 Python 中对 1M 条记录进行排序的最佳方法
【发布时间】:2009-07-24 21:25:24
【问题描述】:

我有一个运行的服务,它获取大约 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 是弹出)

* EDIT * 这个服务是一个 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
]

我一直在使用字典 存储数据,这样我就不会结束 有重复。之后 字典更新为 新的/修改过的数据我都使用了 他们。

* 结束编辑 *

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

【问题讨论】:

  • 这可能不是您要寻找的答案,但使用纯 Python 处理如此大量的数据通常不是一个好主意。当您需要执行大量小操作(例如,排序期间的比较)时,它不是为提高性能而设计的。
  • @Pavel,你错了:Python 的排序 (timsort) 可能是可用的最快的内存排序。 Josh Bloch 在 Google 的一次技术演讲中看到了它的解释,并立即开始将其编码为下一个 Java 版本的内部排序;见bugs.sun.com/bugdatabase/view_bug.do?bug_id=6804124svn.python.org/projects/python/trunk/Objects/listsort.txt
  • @alex,你知道哪个技术讲座吗?不是我怀疑你。它刚刚达到了我的兴趣。 :)
  • 排序本身可以使用已知最快的算法来实现。但是,如果对于列表中的每个元素,它必须首先检索用于排序的键,这有什么关系。

标签: python


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    但是,Python 中似乎没有其中之一。我无法解释;这是任何语言的核心、基本数据类型。 Python 的 dict 和 set 都是无序容器,映射到哈希表的基本数据结构。它绝对应该有一个优化的树数据结构;你可以用它们做很多用哈希表做不到的事情,而且很难很好地实现,所以人们通常不想自己做。

    (也没有映射到链表,它也应该是核心数据类型。不,双端队列不等价。)

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

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

    【讨论】:

    • 没错,本例中的关键组件是“heapq”:docs.python.org/library/heapq.html
    • 优先级队列在这里并不是真正正确的数据结构——应该是 b-tree、rb-tree 等。
    【解决方案3】:

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

    【讨论】:

    • 我想过这个。问题是我会不断地向服务中添加新数据。一些数据将是新的(意味着唯一的“id”),而一些将被更新(相同的“id”)。因此,我不能只将元组添加到列表中进行排序。至少,除非有更好的方法来避免重复 id 条目,否则不会。
    • @sberry2。请使用此新信息更新您的问题。请提供一个示例,说明您希望这种多次出现的事情如何工作。
    • 你能不能用一本包含id的字典 -> tuple 而不是id -> dictionary?在元组中也粘贴id,然后只对项目进行排序?我从 OP 那里得到的印象是数据每次都是从头开始构建的。如果没有,可能值得试一试 SQLite 或其他数据库模块。无论如何,使用内存数据库甚至值得一试。它可能看起来非常重量级,但它已针对此类任务进行了优化。
    【解决方案4】:

    这似乎相当快。

    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 分钟。将在答案中发布更多详细信息,因为它会占用比此处容纳更多的空间。
    【解决方案5】:

    与其尝试保持列表有序,不如使用堆队列来解决问题。它允许您推送任何项目,将“最小”的项目保持在 h[0],弹出该项目(并“冒泡”下一个最小的项目)是 O(nlogn) 操作。

    所以,问问自己:

    • 我是否需要一直订购整个列表? :使用有序结构(如 Zope 的 BTree 包,如 mentioned 由 Ealdwulf 提供)

    • 或整个列表排序但仅在一天的随机插入工作之后?:像你正在做的那样使用排序,或者像S.Lott's answer

    • 或者任何时候只有几个“最小”的项目? :使用heapq

    【讨论】:

    • 我一直在阅读 Zope 的 BTree 包上的文档(已经安装了 Zope),虽然这似乎是一个很好的解决方案,但我不清楚我将在其中存储哪些数据以便我可以保持唯一的“id”值并保持正确排序。有什么见解吗?
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:
      sorted(myLists[key], key=mylists[key].get, reverse=True)
      

      应该可以为您节省一些时间,虽然不是很多。

      【讨论】:

        【解决方案8】:

        我会考虑使用不同的排序算法。诸如合并排序之类的东西可能会起作用。将列表分解为较小的列表并单独排序。然后循环。

        伪代码:

        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)
        

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            我已经对原始方式和 SLott 的提议进行了一些快速分析。在这两种情况下,每个字段都不需要 5-10 分钟。实际排序不是问题。看起来大部分时间都花在了收集数据和转换数据上。此外,我的内存使用量也在飙升——我的 python 内存超过 350 兆!你确定你没有用完所有的内存和分页到磁盘吗?即使使用我那蹩脚的 3 岁节能处理器笔记本电脑,我看到为一百万个项目排序的每个键不到 5-10 分钟的结果。我无法解释的是实际 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)
            

            【讨论】:

              【解决方案11】:

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

              【讨论】:

              • 语言没有快也没有慢,算法和实现才是。
              • 我们不需要那么多反对票——我碰巧不同意 dz 的建议,但事实上,鉴于 存在于现实世界中的实现,某些语言会帮助您比其他人更有效地完成任务。如果您的最大输入是无限大的,那么算法的选择只是唯一的首要因素。
              猜你喜欢
              • 1970-01-01
              • 2021-08-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-07-07
              • 2018-09-03
              • 2010-09-07
              • 1970-01-01
              相关资源
              最近更新 更多