【问题标题】:Merge sorted lists in python在python中合并排序列表
【发布时间】:2010-11-12 14:07:41
【问题描述】:

我有一堆排序的对象列表和一个比较函数

class Obj :
    def __init__(p) :
        self.points = p
def cmp(a, b) :
    return a.points < b.points

a = [Obj(1), Obj(3), Obj(8), ...]
b = [Obj(1), Obj(2), Obj(3), ...]
c = [Obj(100), Obj(300), Obj(800), ...]

result = magic(a, b, c)
assert result == [Obj(1), Obj(1), Obj(2), Obj(3), Obj(3), Obj(8), ...]

magic 看起来像什么?我目前的实现是

def magic(*args) :
    r = []
    for a in args : r += a
    return sorted(r, cmp)

但那是相当低效的。更好的答案?

【问题讨论】:

标签: python arrays merge sorting


【解决方案1】:

给你:一个功能齐全的列表合并排序(改编自我的排序here):

def merge(*args):
    import copy
    def merge_lists(left, right):
        result = []
        while left and right:
            which_list = (left if left[0] <= right[0] else right)
            result.append(which_list.pop(0))
        return result + left + right
    lists = list(args)
    while len(lists) > 1:
        left, right = copy.copy(lists.pop(0)), copy.copy(lists.pop(0))
        result = merge_lists(left, right)
        lists.append(result)
    return lists.pop(0)

这样称呼它:

merged_list = merge(a, b, c)
for item in merged_list:
    print item

为了更好的衡量,我将对您的 Obj 类进行一些更改:

class Obj(object):
    def __init__(self, p) :
        self.points = p
    def __cmp__(self, b) :
        return cmp(self.points, b.points)
    def __str__(self):
        return "%d" % self.points
  • 从对象派生
  • self 传递给__init__()
  • __cmp__ 设为成员函数
  • 添加str() 成员函数以将Obj 呈现为字符串

【讨论】:

    【解决方案2】:

    下面是一个在 O(n) 比较中运行的函数示例。

    您可以通过创建 a 和 b 迭代器并递增它们来加快速度。

    我只是调用了两次函数来合并 3 个列表:

    def zip_sorted(a, b):
        '''
        zips two iterables, assuming they are already sorted
        '''
        i = 0
        j = 0
        result = []
        while i < len(a) and j < len(b):
            if a[i] < b[j]:
                result.append(a[i])
                i += 1
            else:
                result.append(b[j])
                j += 1
        if i < len(a):
            result.extend(a[i:])
        else:
            result.extend(b[j:])
        return result
    
    def genSortedList(num,seed):
        result = [] 
        for i in range(num):
            result.append(i*seed)
        return result
    
    if __name__ == '__main__':
        a = genSortedList(10000,2.0)
        b = genSortedList(6666,3.0)
        c = genSortedList(5000,4.0)
        d = zip_sorted(zip_sorted(a,b),c)
        print d
    

    但是,heapq.merge 混合使用了这种方法并堆放所有列表的当前元素,因此性能应该更好

    【讨论】:

      【解决方案3】:

      我问了一个类似的问题,得到了一些很好的答案:

      该问题的最佳解决方案是合并算法的变体,您可以在此处阅读:

      【讨论】:

        【解决方案4】:

        我喜欢 Roberto Liffredo 的回答。我不知道 heapq.merge()。嗯。

        以下是使用 Roberto 领导的完整解决方案的样子:

        class Obj(object):
            def __init__(self, p) :
                self.points = p
            def __cmp__(self, b) :
                return cmp(self.points, b.points)
            def __str__(self):
                return "%d" % self.points
        
        a = [Obj(1), Obj(3), Obj(8)]
        b = [Obj(1), Obj(2), Obj(3)]
        c = [Obj(100), Obj(300), Obj(800)]
        
        import heapq
        
        sorted = [item for item in heapq.merge(a,b,c)]
        for item in sorted:
            print item
        

        或者:

        for item in heapq.merge(a,b,c):
            print item
        

        【讨论】:

          【解决方案5】:

          使用排序的单行解决方案:

          def magic(*args):
            return sorted(sum(args,[]), key: lambda x: x.points)
          

          IMO 这个解决方案可读性很强。

          使用 heapq 模块可能会更高效,但我没有测试过。不能在 heapq 中指定 cmp/key 函数,所以必须实现 Obj 才能进行隐式排序。

          import heapq
          def magic(*args):
            h = []
            for a in args:
              heapq.heappush(h,a)
            return [i for i in heapq.heappop(h)
          

          【讨论】:

          • 你的 heapq 方法一团糟。您正在推送整个列表而不是它们的项目,并且您忽略了关键。不过,一个班轮很酷。
          • 是的,你是对的,我只使用了几次 heapq,并没有将它粘贴到控制台进行测试。我的错,对不起。虽然现在我看到必须将 Obj 对象定义为“可排序”才能使 heapq 工作,因为您不能在 heapq 中指定 cmp/key 函数。
          • 这段代码一团糟。两个 sn-ps 都有语法错误,并且使用 sum 来连接列表是非常低效的。更不用说有 operator.attrgetter 来代替 lambda。
          【解决方案6】:

          您可以使用 [heap](http://en.wikipedia.org/wiki/Heap_(data_structure).

          插入是O(log(n)),所以合并a、b和c会是O(n log(n))

          在 Python 中,您可以使用 heapq module

          【讨论】:

          • +1:对列表进行排序本来就低效:通过使用更智能的结构来防止排序。
          • @OrganicPanda:你看答案了吗?它说heapq 摊销了分拣成本。这是一个更智能的结构。也考虑一下。累积三个单独的集合似乎很愚蠢。为什么不积累一个可变对象的哈希值;这可以由其他来源的对象更新。现在“比较”没有实际意义,因为所有对象都已正确关联,无需任何排序。
          • @S.Lott 我的道歉 - 我以为你是在建议你自己的更聪明的答案,但没有解释它..我的错
          【解决方案7】:

          Python 标准库为此提供了一种方法:heapq.merge
          正如文档所说,它与使用 itertools 非常相似(但有更多限制);如果你不能忍受这些限制(或者如果你不使用 Python 2.6),你可以这样做:

          sorted(itertools.chain(args), cmp)
          

          但是,我认为它与您自己的解决方案具有相同的复杂性,尽管使用迭代器应该可以提供一些非常好的优化和速度提升。

          【讨论】:

          • 使用 key 而不是 cmp 应该是首选(并且应该更快)。 Python3 无论如何都没有 cmp 参数。
          • 其实我只是用了和OP一样的格式,但是你说的很对,key应该优先于cmp
          • 嗯,OP 的 cmp 函数是错误的,不起作用。如果您使用的是 heapq,则必须在您的类上提供 lt 等方法,或者在您的堆中使用 (sorting key, object) 的元组。
          • 我认为你的意思是 itertools.chain(*args)。你写的相当于sorted(iter(args), cmp),意义不大。
          • 如果我理解正确的话,对串联列表进行排序是复杂的Θ(n.log n)(通过代码示例提出的解决方案),但合并(排序的)列表是Θ(n)。差别不小。
          【解决方案8】:

          使用bisect 模块。来自文档:“此模块支持按排序顺序维护列表,而无需在每次插入后对列表进行排序。”

          import bisect
          
          def magic(*args):
              r = []
              for a in args:
                  for i in a:
                      bisect.insort(r, i)
              return r
          

          【讨论】:

            【解决方案9】:

            我不知道它是否会更快,但您可以将其简化为:

            def GetObjKey(a):
                return a.points
            
            return sorted(a + b + c, key=GetObjKey)
            

            如果您愿意,当然也可以使用cmp 而不是key

            【讨论】:

              猜你喜欢
              • 2020-10-25
              • 1970-01-01
              • 2022-10-05
              • 2014-12-02
              • 2012-01-18
              • 1970-01-01
              • 2018-05-16
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多