在Python中组合两个排序列表

时间:2009-01-21 07:33:48

标签: python list sorting

我有两个对象列表。每个列表已经按日期时间类型的对象的属性进行排序。我想将这两个列表合并为一个排序列表。是进行排序的最好方法还是在Python中有更聪明的方法?

23 个答案:

答案 0 :(得分:109)

人们似乎过度复杂了。只需合并两个列表,然后对它们进行排序:

>>> l1 = [1, 3, 4, 7]
>>> l2 = [0, 2, 5, 6, 8, 9]
>>> l1.extend(l2)
>>> sorted(l1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

..或更短(并且不修改l1):

>>> sorted(l1 + l2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

..容易!另外,它只使用两个内置函数,因此假设列表的大小合理,它应该比在循环中实现排序/合并更快。更重要的是,上面代码少得多,而且非常易读。

如果您的列表很大(我估计会超过几十万),使用替代/自定义排序方法可能会更快,但可能会首先进行其他优化(例如,不存储数百万{ {1}}对象)

使用datetime(重复函数1000000次),我将其与ghoseb's解决方案进行了基准测试,timeit.Timer().repeat()显着更快:

sorted(l1+l2)接过..

merge_sorted_lists

[9.7439379692077637, 9.8844599723815918, 9.552299976348877] 接过..

sorted(l1+l2)

答案 1 :(得分:98)

  

在Python中有更聪明的方法吗

这没有被提及,所以我将继续 - 在python 2.6+的heapq模块中有一个merge stdlib function。如果您要做的就是完成任务,这可能是一个更好的主意。当然,如果你想实现自己的,合并排序的合并是可行的方法。

>>> list1 = [1, 5, 8, 10, 50]
>>> list2 = [3, 4, 29, 41, 45, 49]
>>> from heapq import merge
>>> list(merge(list1, list2))
[1, 3, 4, 5, 8, 10, 29, 41, 45, 49, 50]

这是the documentation

答案 2 :(得分:50)

长话短说,除非len(l1 + l2) ~ 1000000使用:

L = l1 + l2
L.sort()

merge vs. sort comparison

可以找到图形和源代码的说明here

该图由以下命令生成:

$ python make-figures.py --nsublists 2 --maxn=0x100000 -s merge_funcs.merge_26 -s merge_funcs.sort_builtin

答案 3 :(得分:25)

这只是合并。将每个列表视为堆栈,并连续弹出两个堆栈头中较小的一个,将项添加到结果列表中,直到其中一个堆栈为空。然后将所有剩余项添加到结果列表中。

答案 4 :(得分:15)

ghoseb's解决方案存在轻微缺陷,使其为O(n ** 2),而不是O(n)。
问题是这是在执行:

item = l1.pop(0)

使用链接列表或deques这将是一个O(1)操作,因此不会影响复杂性,但由于python列表是作为向量实现的,因此复制l1剩余的一个空格的剩余元素,一个O( n)操作。由于每次都通过列表,因此将O(n)算法转换为O(n ** 2)算法。这可以通过使用不改变源列表的方法来纠正,但只是跟踪当前位置。

我已根据dbr

的建议尝试对校正算法进行基准测试,然后对简单排序(l1 + l2)进行基准测试
def merge(l1,l2):
    if not l1:  return list(l2)
    if not l2:  return list(l1)

    # l2 will contain last element.
    if l1[-1] > l2[-1]:
        l1,l2 = l2,l1

    it = iter(l2)
    y = it.next()
    result = []

    for x in l1:
        while y < x:
            result.append(y)
            y = it.next()
        result.append(x)
    result.append(y)
    result.extend(it)
    return result

我用

生成的列表测试了这些
l1 = sorted([random.random() for i in range(NITEMS)])
l2 = sorted([random.random() for i in range(NITEMS)])

对于各种大小的列表,我得到以下时间(重复100次):

# items:  1000   10000 100000 1000000
merge  :  0.079  0.798 9.763  109.044 
sort   :  0.020  0.217 5.948  106.882

所以事实上,看起来dbr是正确的,只是使用sorted()是可取的,除非你期望非常大的列表,尽管它的算法复杂度更差。收支平衡点在每个源列表中大约有一百万个项目(总计200万)。

合并方法的一个优点是,重写为生成器是微不足道的,它将使用更少的内存(不需要中间列表)。

<强> [编辑] 我已经在接近问题的情况下重试了这个问题 - 使用包含字段“date”的对象列表,该字段是日期时间对象。 上述算法已更改为与.date进行比较,而排序方法已更改为:

return sorted(l1 + l2, key=operator.attrgetter('date'))

这确实改变了一些事情。比较更昂贵意味着我们执行的数量相对于实现的恒定时间速度变得更加重要。这意味着合并弥补了失地,超过了100,000个项目的sort()方法。基于更复杂的对象(例如大字符串或列表)进行比较可能会更加平衡这种平衡。

# items:  1000   10000 100000  1000000[1]
merge  :  0.161  2.034 23.370  253.68
sort   :  0.111  1.523 25.223  313.20

[1]:注意:我实际上只对1,000,000件商品进行了10次重复,并且相应地按比例放大,因为它很慢。

答案 5 :(得分:4)

这是两个排序列表的简单合并。看一下下面的示例代码,它会合并两个排序的整数列表。

#!/usr/bin/env python
## merge.py -- Merge two sorted lists -*- Python -*-
## Time-stamp: "2009-01-21 14:02:57 ghoseb"

l1 = [1, 3, 4, 7]
l2 = [0, 2, 5, 6, 8, 9]

def merge_sorted_lists(l1, l2):
    """Merge sort two sorted lists

    Arguments:
    - `l1`: First sorted list
    - `l2`: Second sorted list
    """
    sorted_list = []

    # Copy both the args to make sure the original lists are not
    # modified
    l1 = l1[:]
    l2 = l2[:]

    while (l1 and l2):
        if (l1[0] <= l2[0]): # Compare both heads
            item = l1.pop(0) # Pop from the head
            sorted_list.append(item)
        else:
            item = l2.pop(0)
            sorted_list.append(item)

    # Add the remaining of the lists
    sorted_list.extend(l1 if l1 else l2)

    return sorted_list

if __name__ == '__main__':
    print merge_sorted_lists(l1, l2)

这应该适用于datetime对象。希望这会有所帮助。

答案 6 :(得分:4)

from datetime import datetime
from itertools import chain
from operator import attrgetter

class DT:
    def __init__(self, dt):
        self.dt = dt

list1 = [DT(datetime(2008, 12, 5, 2)),
         DT(datetime(2009, 1, 1, 13)),
         DT(datetime(2009, 1, 3, 5))]

list2 = [DT(datetime(2008, 12, 31, 23)),
         DT(datetime(2009, 1, 2, 12)),
         DT(datetime(2009, 1, 4, 15))]

list3 = sorted(chain(list1, list2), key=attrgetter('dt'))
for item in list3:
    print item.dt

输出:

2008-12-05 02:00:00
2008-12-31 23:00:00
2009-01-01 13:00:00
2009-01-02 12:00:00
2009-01-03 05:00:00
2009-01-04 15:00:00

我敢打赌,这比任何花哨的纯Python合并算法都快,即使对于大数据也是如此。 Python 2.6的heapq.merge是另一个故事。

答案 7 :(得分:3)

Python的排序实现“timsort”专门针对包含有序部分的列表进行了优化。另外,它是用C语言写的。

http://bugs.python.org/file4451/timsort.txt
http://en.wikipedia.org/wiki/Timsort

正如人们所提到的,它可能会通过一些常数因子将比较函数调用更多次(但在许多情况下可能在更短的时间内调用它更多次!)。

  但是,我永远不会依赖于此。 - 丹尼尔纳达西

我相信Python开发人员致力于保持timsort,或者至少在这种情况下保持O(n)的排序。

  

广义排序(即将有限值域中的基数排序分开)
  在串行机器上不能以小于O(n log n)的速度完成。 - 巴里凯利

对,在一般情况下排序不能比这快。但由于O()是一个上界,因此任意输入上的时间输出为O(n log n),与给定排序(L1)+排序(L2)的O(n)不相矛盾。

答案 8 :(得分:2)

Merge Sort中合并步骤的实现,它遍历两个列表:

def merge_lists(L1, L2):
    """
    L1, L2: sorted lists of numbers, one of them could be empty.

    returns a merged and sorted list of L1 and L2.
    """

    # When one of them is an empty list, returns the other list
    if not L1:
        return L2
    elif not L2:
        return L1

    result = []
    i = 0
    j = 0

    for k in range(len(L1) + len(L2)):
        if L1[i] <= L2[j]:
            result.append(L1[i])
            if i < len(L1) - 1:
                i += 1
            else:
                result += L2[j:]  # When the last element in L1 is reached,
                break             # append the rest of L2 to result.
        else:
            result.append(L2[j])
            if j < len(L2) - 1:
                j += 1
            else:
                result += L1[i:]  # When the last element in L2 is reached,
                break             # append the rest of L1 to result.

    return result

L1 = [1, 3, 5]
L2 = [2, 4, 6, 8]
merge_lists(L1, L2)               # Should return [1, 2, 3, 4, 5, 6, 8]
merge_lists([], L1)               # Should return [1, 3, 5]

我还在学习算法,请告诉我代码是否可以在任何方面得到改进,感谢您的反馈,谢谢!

答案 9 :(得分:2)

def merge_sort(a,b):

    pa = 0
    pb = 0
    result = []

    while pa < len(a) and pb < len(b):
        if a[pa] <= b[pb]:
            result.append(a[pa])
            pa += 1
        else:
            result.append(b[pb])
            pb += 1

    remained = a[pa:] + b[pb:]
    result.extend(remained)


return result

答案 10 :(得分:2)

递归实现如下。平均表现为O(n)。

def merge_sorted_lists(A, B, sorted_list = None):
    if sorted_list == None:
        sorted_list = []

    slice_index = 0
    for element in A:
        if element <= B[0]:
            sorted_list.append(element)
            slice_index += 1
        else:
            return merge_sorted_lists(B, A[slice_index:], sorted_list)

    return sorted_list + B

或具有改进的空间复杂性的生成器:

def merge_sorted_lists_as_generator(A, B):
    slice_index = 0
    for element in A:
        if element <= B[0]:
            slice_index += 1
            yield element       
        else:
            for sorted_element in merge_sorted_lists_as_generator(B, A[slice_index:]):
                yield sorted_element
            return        

    for element in B:
        yield element

答案 11 :(得分:1)

嗯,天真的方法(将2个列表组合成大型和排序)将是O(N * log(N))复杂度。另一方面,如果你手动实现合并(我不知道python库中的任何现成代码,但我不是专家),复杂性将是O(N),这显然更快。 Barry Kelly在帖子中很好地描述了这个想法。

答案 12 :(得分:1)

已使用合并排序的合并步骤。但是我使用了生成器时间复杂度 O(n)

def merge(lst1,lst2):
    len1=len(lst1)
    len2=len(lst2)
    i,j=0,0
    while(i<len1 and j<len2):
        if(lst1[i]<lst2[j]):
                yield lst1[i]
                i+=1
        else:
                yield lst2[j]
                j+=1
    if(i==len1):
        while(j<len2):
                yield lst2[j]
                j+=1
    elif(j==len2):
        while(i<len1):
                yield lst1[i]
                i+=1
l1=[1,3,5,7]
l2=[2,4,6,8,9]
mergelst=(val for val in merge(l1,l2))
print(*mergelst)

答案 13 :(得分:1)

import random

    n=int(input("Enter size of table 1")); #size of list 1
    m=int(input("Enter size of table 2")); # size of list 2
    tb1=[random.randrange(1,101,1) for _ in range(n)] # filling the list with random
    tb2=[random.randrange(1,101,1) for _ in range(m)] # numbers between 1 and 100
    tb1.sort(); #sort the list 1 
    tb2.sort(); # sort the list 2
    fus=[]; # creat an empty list
    print(tb1); # print the list 1
    print('------------------------------------');
    print(tb2); # print the list 2
    print('------------------------------------');
    i=0;j=0;  # varialbles to cross the list
    while(i<n and j<m):
        if(tb1[i]<tb2[j]):
            fus.append(tb1[i]); 
            i+=1;
        else:
            fus.append(tb2[j]);
            j+=1;

    if(i<n):
        fus+=tb1[i:n];
    if(j<m):
        fus+=tb2[j:m];

    print(fus);

  # this code is used to merge two sorted lists in one sorted list (FUS) without
  #sorting the (FUS)

答案 14 :(得分:1)

如果您想以更加一致的方式学习迭代中的内容,请尝试使用

def merge_arrays(a, b):
    l= []

    while len(a) > 0 and len(b)>0:
        if a[0] < b[0]: l.append(a.pop(0))    
        else:l.append(b.pop(0))

    l.extend(a+b)
    print( l )

答案 15 :(得分:1)

使用合并排序的“合并”步骤,它在O(n)时间内运行。

来自wikipedia(伪代码):

function merge(left,right)
    var list result
    while length(left) > 0 and length(right) > 0
        if first(left) ≤ first(right)
            append first(left) to result
            left = rest(left)
        else
            append first(right) to result
            right = rest(right)
    end while
    while length(left) > 0 
        append left to result
    while length(right) > 0 
        append right to result
    return result

答案 16 :(得分:0)

def compareDate(obj1, obj2):
    if obj1.getDate() < obj2.getDate():
        return -1
    elif obj1.getDate() > obj2.getDate():
        return 1
    else:
        return 0



list = list1 + list2
list.sort(compareDate)

将列表排序到位。定义自己的函数来比较两个对象,并将该函数传递给内置的排序函数。

不要使用冒泡排序,它有可怕的性能。

答案 17 :(得分:0)

这是我在线性时间的解决方案,无需编辑l1和l2:

def merge(l1, l2):
  m, m2 = len(l1), len(l2)
  newList = []
  l, r = 0, 0
  while l < m and r < m2:
    if l1[l] < l2[r]:
      newList.append(l1[l])
      l += 1
    else:
      newList.append(l2[r])
      r += 1
  return newList + l1[l:] + l2[r:]

答案 18 :(得分:0)

此代码的时间复杂度为O(n),并且可以合并任何数据类型的列表,并具有量化功能作为参数func。它会生成一个新的合并列表,并且不会修改任何作为参数传递的列表。

def merge_sorted_lists(listA,listB,func):
    merged = list()
    iA = 0
    iB = 0
    while True:
        hasA = iA < len(listA)
        hasB = iB < len(listB)
        if not hasA and not hasB:
            break
        valA = None if not hasA else listA[iA]
        valB = None if not hasB else listB[iB]
        a = None if not hasA else func(valA)
        b = None if not hasB else func(valB)
        if (not hasB or a<b) and hasA:
            merged.append(valA)
            iA += 1
        elif hasB:
            merged.append(valB)
            iB += 1
    return merged

答案 19 :(得分:0)

我对这个问题的看法:

x

答案 20 :(得分:0)

O(m+n) 复杂度

def merge_sorted_list(nums1: list, nums2:list) -> list:
        m = len(nums1)
        n = len(nums2)
        
        nums1 = nums1.copy()
        nums2 = nums2.copy()
        nums1.extend([0 for i in range(n)])
        while m > 0 and n > 0:
            if nums1[m-1] >= nums2[n-1]:
                nums1[m+n-1] = nums1[m-1]
                m -= 1
            else:
                nums1[m+n-1] = nums2[n-1]
                n -= 1
        if n > 0:
            nums1[:n] = nums2[:n]
        return nums1

l1 = [1, 3, 4, 7]    
l2 =  [0, 2, 5, 6, 8, 9]    
print(merge_sorted_list(l1, l2))

输出

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

答案 21 :(得分:0)

如果您有一个小列表,只需合并它们并使用内置函数进行排序,如下所示:

def in_built_sort(list1, list2):
    // time complexity : O(nlog n) 
    return sorted(list1 + list2)

但是如果您在两个列表中都有 MILLION 或 BILLION 元素,请使用此方法:

def custom_sort(list1, list2):
    // time complexity : O(n)
    merged_list_size = len(list1) + len(list2)
    merged_list = [None] * merged_list_size

    current_index_list1 = 0
    current_index_list2 = 0
    current_index_merged = 0
    while current_index_merged < merged_list_size:
        if (not current_index_list1 >= len(list1) and (current_index_list2 >= len(list2) or list1[current_index_list1] < list2[current_index_list2])):
            merged_list[current_index_merged] = list1[current_index_list1]
            current_index_list1 += 1
        else:
            merged_list[current_index_merged] = list2[current_index_list2]
            current_index_list2 += 1

        current_index_merged += 1
    return merged_list

性能测试: 对两个列表中的 1000 万个随机元素进行测试并重复 10 次。

print(timeit.timeit(stmt=in_built_sort, number=10))  // 250.44088469999997  O(nlog n)   
print(timeit.timeit(stmt=custom_sort, number=10)) // 125.82338159999999  O(n)

答案 22 :(得分:-1)

希望这会有所帮助。非常简单直接:

l1 = [1、3、4、7]

l2 = [0,2,5,6,8,9]

l3 = l1 + l2

l3.sort()

打印(l3)

[0,1,2,3,4,5,6,7,8,9]